Skip to content

Commit

Permalink
feat: Insert test variables and sleeps in script (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
going-confetti authored Jul 23, 2024
1 parent 7030e13 commit 92a49c2
Show file tree
Hide file tree
Showing 16 changed files with 262 additions and 190 deletions.
76 changes: 64 additions & 12 deletions src/codegen/codegen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CorrelationStateMap, TestRule } from '@/types/rules'
import { generateSequentialInt } from '@/rules/utils'
import { ProxyData } from '@/types'
import { correlationRecording } from '@/test/fixtures/correlationRecording'
import { ThinkTime } from '@/types/testOptions'

describe('Code generation', () => {
describe('generateScript', () => {
Expand All @@ -19,26 +20,56 @@ describe('Code generation', () => {
export const options = {}
export default function() {
let params
let resp
let match
let regex
sleep(1)
}
`

expect(
generateScript({
recording: {},
rules: [],
variables: {},
generator: {
name: 'test',
version: '0',
recordingPath: 'test',
options: {
loadProfile: {
executor: 'shared-iterations',
startTime: '0',
vus: 1,
iterations: 1,
maxDuration: '1',
},
thinkTime: {
sleepType: 'iterations',
timing: {
type: 'fixed',
value: 1,
},
},
},
testData: {
variables: [],
},
rules: [],
allowlist: [],
},
}).replace(/\s/g, '')
).toBe(expectedResult.replace(/\s/g, ''))
})
})

describe('generateVariableDeclarations', () => {
it('should generate variable declarations', () => {
const variables = {
test: 'test',
}
const variables = [
{
name: 'test',
value: 'test',
},
]

const expectedResult = `
const test = "test"
Expand Down Expand Up @@ -76,17 +107,26 @@ describe('Code generation', () => {
const rules: TestRule[] = []
const correlationStateMap: CorrelationStateMap = {}
const sequentialIdGenerator = generateSequentialInt()
const thinkTime: ThinkTime = {
sleepType: 'iterations',
timing: {
type: 'fixed',
value: 1,
},
}

const expectedResult = `
resp = http.request('GET', \`/api/v1/users\`, null, {})
params = { headers: {}, cookies: {} }
resp = http.request('GET', \`/api/v1/users\`, null, params)
`

expect(
generateRequestSnippets(
recording,
rules,
correlationStateMap,
sequentialIdGenerator
sequentialIdGenerator,
thinkTime
).replace(/\s/g, '')
).toBe(expectedResult.replace(/\s/g, ''))
})
Expand All @@ -108,16 +148,27 @@ describe('Code generation', () => {
]
const correlationStateMap: CorrelationStateMap = {}
const sequentialIdGenerator = generateSequentialInt()
const thinkTime: ThinkTime = {
sleepType: 'iterations',
timing: {
type: 'fixed',
value: 1,
},
}

const expectedResult = `
resp = http.request('POST', \`http://test.k6.io/api/v1/foo\`, null, {})
params = { headers: {}, cookies: {} }
resp = http.request('POST', \`http://test.k6.io/api/v1/foo\`, null, params)
resp = http.request('POST', \`http://test.k6.io/api/v1/login\`, null, {})
params = { headers: {}, cookies: {} }
resp = http.request('POST', \`http://test.k6.io/api/v1/login\`, null, params)
let correl_0 = resp.json().user_id
resp = http.request('GET', \`http://test.k6.io/api/v1/users/\${correl_0}\`, null, {})
params = { headers: {}, cookies: {} }
resp = http.request('GET', \`http://test.k6.io/api/v1/users/\${correl_0}\`, null, params)
resp = http.request('POST', \`http://test.k6.io/api/v1/users\`, \`${JSON.stringify({ user_id: '${correl_0}' })}\`, {})
params = { headers: {}, cookies: {} }
resp = http.request('POST', \`http://test.k6.io/api/v1/users\`, \`${JSON.stringify({ user_id: '${correl_0}' })}\`, params)
`

Expand All @@ -126,7 +177,8 @@ describe('Code generation', () => {
correlationRecording,
rules,
correlationStateMap,
sequentialIdGenerator
sequentialIdGenerator,
thinkTime
).replace(/\s/g, '')
).toBe(expectedResult.replace(/\s/g, ''))
})
Expand Down
138 changes: 70 additions & 68 deletions src/codegen/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,52 @@ import { GroupedProxyData, ProxyData, RequestSnippetSchema } from '@/types'
import { CorrelationStateMap, TestRule } from '@/types/rules'
import { applyRule } from '@/rules/rules'
import { generateSequentialInt } from '@/rules/utils'
import { GeneratorFileData } from '@/types/generator'
import { Variable } from '@/types/testData'
import { TestOptions, ThinkTime } from '@/types/testOptions'
import { exhaustive } from '@/utils/typescript'

interface GenerateScriptParams {
recording: GroupedProxyData
rules: TestRule[]
variables?: Record<string, string>
generator: GeneratorFileData
}

/**
* Generates a k6 script from the recording and rules
* @param {GenerateScriptParams} params - The parameters object
* @param {GroupedProxyData} params.recording - The recording
* @param {TestRule[]} params.rules - The set of rules to apply to the recording
* @param {Record<string, string>} [params.variables] - The variables to include in the script
* @returns {string}
*/
export function generateScript({
recording,
rules,
variables = {},
generator,
}: GenerateScriptParams): string {
return `
import { group, sleep } from 'k6'
import http from 'k6/http'
export const options = ${generateOptions()}
export const options = ${generateOptions(generator.options)}
${generateVariableDeclarations(variables)}
${generateVariableDeclarations(generator.testData.variables)}
export default function() {
let resp
${generateVUCode(recording, rules)}
${generateVUCode(recording, generator.rules, generator.options.thinkTime)}
}
`
}

/**
* Generates the options object for the k6 script
* @returns {string}
*/
export function generateOptions(): string {
export function generateOptions(options: TestOptions): string {
console.log(options)
return '{}'
}

/**
* Generates declarations for test variables
* @param {Record<string, string>} variables - The variables to include in the script
* @returns {string}
*/
export function generateVariableDeclarations(
variables: Record<string, string>
): string {
return Object.entries(variables)
.map(([key, value]) => `const ${key} = "${value}"`)
export function generateVariableDeclarations(variables: Variable[]): string {
return variables
.filter(({ name }) => name)
.map(({ name, value }) => `const ${name} = "${value}"`)
.join('\n')
}

/**
* Generates the VU code for the k6 script
* @param recording - The recording
* @param rules - The set of rules to apply to the recording
* @returns {string}
*/
export function generateVUCode(
recording: GroupedProxyData,
rules: TestRule[]
rules: TestRule[],
thinkTime: ThinkTime
): string {
const groups = Object.entries(recording)
const isSingleGroup = groups.length === 1
const correlationStateMap: CorrelationStateMap = {}
const sequentialIdGenerator = generateSequentialInt()

Expand All @@ -79,28 +57,32 @@ export function generateVUCode(
recording,
rules,
correlationStateMap,
sequentialIdGenerator
sequentialIdGenerator,
thinkTime
)
return isSingleGroup
? requestSnippets
: generateGroupSnippet(groupName, requestSnippets)

return generateGroupSnippet(groupName, requestSnippets, thinkTime)
})
.join(`\n`)

return [groupSnippets, 'sleep(1)'].join('\n')
return [
`
let params
let resp
let match
let regex
`,
groupSnippets,
thinkTime.sleepType === 'iterations' ? generateSleep(thinkTime.timing) : '',
].join('\n')
}

/**
* Generates request snippets for a single group
* @param recording - The recording of a single group
* @param rules - The set of rules to apply to the recording
* @returns {string}
*/
export function generateRequestSnippets(
recording: ProxyData[],
rules: TestRule[],
correlationStateMap: CorrelationStateMap,
sequentialIdGenerator: Generator<number>
sequentialIdGenerator: Generator<number>,
thinkTime: ThinkTime
): string {
return recording.reduce((acc, data) => {
const requestSnippetSchema = rules.reduce<RequestSnippetSchema>(
Expand All @@ -111,28 +93,23 @@ export function generateRequestSnippets(

const requestSnippet = generateSingleRequestSnippet(requestSnippetSchema)

return `${acc}\n${requestSnippet}`
return `${acc}
${requestSnippet}
${thinkTime.sleepType === 'requests' ? `${generateSleep(thinkTime.timing)}` : ''}`
}, '')
}

/**
*
* @param groupName - The name of the group
* @param requestSnippets - The request snippets
* @returns {string}
*/
export function generateGroupSnippet(
groupName: string,
requestSnippets: string
requestSnippets: string,
thinkTime: ThinkTime
): string {
return `group('${groupName}', function() {${requestSnippets}});`
return `group('${groupName}', function() {
${requestSnippets}
${thinkTime.sleepType === 'groups' ? `${generateSleep(thinkTime.timing)}` : ''}
});`
}

/**
* Generates a single HTTP request snippet
* @param {RequestSnippetSchema} requestSnippetSchema
* @returns {string}
*/
export function generateSingleRequestSnippet(
requestSnippetSchema: RequestSnippetSchema
): string {
Expand All @@ -155,11 +132,36 @@ export function generateSingleRequestSnippet(
console.error('Failed to serialize request content', error)
}

const params = '{}'
console.log(request)
const params = `params = ${generateRequestParams(request)}`

const main = `
resp = http.request(${method}, ${url}, ${content}, ${params})
resp = http.request(${method}, ${url}, ${content}, params)
`

return [...before, main, ...after].join('\n')
return [params, ...before, main, ...after].join('\n')
}

function generateSleep(timing: ThinkTime['timing']): string {
switch (timing.type) {
case 'fixed':
return `sleep(${timing.value})`
case 'range':
return `sleep(Math.random() * (${timing.value.max} - ${timing.value.min}) + ${timing.value.min})`
default:
return exhaustive(timing)
}
}

function generateRequestParams(request: ProxyData['request']): string {
return `
{
headers: {
${request.headers.map(([name, value]) => `'${name}': '${value}'`).join(',\n')}
},
cookies: {
${request.cookies.map(([name, value]) => `'${name}': '${value}'`).join(',\n')}
}
}
`
}
4 changes: 0 additions & 4 deletions src/constants/generator.ts

This file was deleted.

8 changes: 4 additions & 4 deletions src/hooks/useGeneratorStore/slices/testOptions/loadProfile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ExecutorType } from '@/constants/generator'
import {
LoadProfileExecutorOptions,
RampingStage,
RampingVUsOptions,
SharedIterationsOptions,
Expand All @@ -14,21 +14,21 @@ interface SharedIterationsState
}

interface CommonState {
executor: ExecutorType
executor: LoadProfileExecutorOptions['executor']
gracefulStop: string | undefined
startTime: string | undefined
}

interface CommonActions {
setExecutor: (value: ExecutorType) => void
setExecutor: (value: LoadProfileExecutorOptions['executor']) => void
setGracefulStop: (value: string | undefined) => void
setStartTime: (value: string | undefined) => void
}

type CommonStore = CommonState & CommonActions

const createCommonSlice: ImmerStateCreator<CommonStore> = (set) => ({
executor: ExecutorType.RampingVUs,
executor: 'ramping-vus',
gracefulStop: undefined,
startTime: undefined,

Expand Down
Loading

0 comments on commit 92a49c2

Please sign in to comment.