Skip to content

Commit

Permalink
Feat/async testing (#223)
Browse files Browse the repository at this point in the history
* Wip

* adds @async annotation support for tests

* mostly done

* realized I had not added multiple groups to the async strategy. rectified that

* update docs, some improvements to reporting and calling done

* by jove, I think it's done

* fixes

* more fixes
  • Loading branch information
georgejecook authored May 6, 2023
1 parent e4ec4be commit c33ac17
Show file tree
Hide file tree
Showing 19 changed files with 4,952 additions and 206 deletions.
23 changes: 21 additions & 2 deletions bsc-plugin/src/lib/rooibos/Annotation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BrsFile, Statement, AnnotationExpression } from 'brighterscript';
import type { AnnotationExpression, BrsFile, Statement } from 'brighterscript';
import { diagnosticIllegalParams, diagnosticNoTestNameDefined, diagnosticMultipleDescribeAnnotations, diagnosticMultipleTestOnFunctionDefined } from '../utils/Diagnostics';

export enum AnnotationType {
Expand All @@ -16,6 +16,7 @@ export enum AnnotationType {
Params = 'params',
IgnoreParams = 'ignoreparams',
SoloParams = 'onlyparams',
Async = 'async',
Tags = 'tags',
NoCatch = 'nocatch',
NoEarlyExit = 'noearlyexit'
Expand All @@ -35,6 +36,7 @@ let annotationLookup = {
params: AnnotationType.Params,
ignoreparams: AnnotationType.IgnoreParams,
onlyparams: AnnotationType.SoloParams,
async: AnnotationType.Async,
tags: AnnotationType.Tags,
nocatch: AnnotationType.NoCatch,
noearlyexit: AnnotationType.NoEarlyExit
Expand All @@ -55,12 +57,14 @@ export class AnnotationParams {
public isIgnore = false,
public isSolo = false,
public noCatch = false,
public noearlyexit = false
public noEarlyexit = false
) {

}
}
export class RooibosAnnotation {
isAsync: boolean;
asyncTimeout: number;

/**
* Represents a group of comments which contain tags such as @only, @suite, @describe, @it etc
Expand Down Expand Up @@ -91,10 +95,12 @@ export class RooibosAnnotation {
let blockAnnotation: RooibosAnnotation;
let testAnnotation: RooibosAnnotation;
let isSolo = false;
let async = false;
let isIgnore = false;
let noCatch = false;
let noEarlyExit = false;
let nodeName = null;
let asyncTimeout = -1;
let tags = [] as string[];
if (statement.annotations?.length) {
let describeAnnotations = statement.annotations.filter((a) => getAnnotationType(a.name) === AnnotationType.Describe);
Expand All @@ -109,6 +115,11 @@ export class RooibosAnnotation {
case AnnotationType.NoEarlyExit:
noEarlyExit = true;
break;
case AnnotationType.Async:
async = true;
//ensure the arg is an integer, if not set to 2000
asyncTimeout = annotation.getArguments().length === 1 ? parseInt(annotation.getArguments()[0] as any) : -1;
break;
case AnnotationType.NoCatch:
noCatch = true;
break;
Expand All @@ -134,23 +145,31 @@ export class RooibosAnnotation {
case AnnotationType.TestSuite:
const groupName = annotation.getArguments()[0] as string;
blockAnnotation = new RooibosAnnotation(file, annotation, annotationType, annotation.name, groupName, isIgnore, isSolo, null, nodeName, tags, noCatch, noEarlyExit);
blockAnnotation.isAsync = async;
blockAnnotation.asyncTimeout = asyncTimeout === -1 ? 60000 : asyncTimeout;
nodeName = null;
isSolo = false;
isIgnore = false;
async = false;
asyncTimeout = -1;
break;
case AnnotationType.It:
const testName = annotation.getArguments()[0] as string;
if (!testName || testName.trim() === '') {
diagnosticNoTestNameDefined(file, annotation);
}
let newAnnotation = new RooibosAnnotation(file, annotation, annotationType, annotation.name, testName, isIgnore, isSolo, undefined, undefined, tags, noCatch);
newAnnotation.isAsync = async;
newAnnotation.asyncTimeout = asyncTimeout === -1 ? 2000 : asyncTimeout;
if (testAnnotation) {
diagnosticMultipleTestOnFunctionDefined(file, newAnnotation.annotation);
} else {
testAnnotation = newAnnotation;
}
isSolo = false;
isIgnore = false;
async = false;
asyncTimeout = -1;
break;
case AnnotationType.Params:
case AnnotationType.SoloParams:
Expand Down
32 changes: 19 additions & 13 deletions bsc-plugin/src/lib/rooibos/TestCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export class TestCase {
params: any[] = null, paramTestIndex = 0, paramLineNumber = 0, expectedNumberOfParams = 0) {
this.annotation = annotation;
this.isSolo = isSolo;
this.isAsync = annotation.isAsync;
this.asyncTimeout = annotation.asyncTimeout;
this.funcName = funcName;
this.isIgnored = isIgnored;
this.name = name;
Expand All @@ -26,6 +28,8 @@ export class TestCase {
public funcName: string;
public isIgnored: boolean;
public isParamTest: boolean;
public isAsync: boolean;
public asyncTimeout: number;
public name: string;
public lineNumber: number;
public paramLineNumber: number;
Expand All @@ -44,6 +48,8 @@ export class TestCase {
noCatch: ${this.annotation.noCatch}
funcName: "${this.funcName || ''}"
isIgnored: ${this.isIgnored}
isAsync: ${this.isAsync}
asyncTimeout: ${this.asyncTimeout || 2000}
isParamTest: ${this.isParamTest}
name: ${sanitizeBsJsonString(this.name)}
lineNumber: ${this.lineNumber + 2}
Expand All @@ -65,24 +71,24 @@ export class TestCase {
fixBadJson(o) {
// In case of an array we'll stringify all objects.
if (Array.isArray(o)) {
return `[${
o
.map(obj => `${this.fixBadJson(obj)}`)
.join(',')
}]`;
return `[${o
.map(obj => `${this.fixBadJson(obj)}`)
.join(',')
// eslint-disable-next-line @typescript-eslint/indent
}]`;
}
// not an object, stringify using native function
if (typeof o !== 'object' || o === null) {
return JSON.stringify(o);
}
return `{${
Object
.keys(o)
.map(key => {
return `"${key.replace(/"/g, '')}":${this.fixBadJson(o[key])}`;
})
.join(',')
}}`;
return `{${Object
.keys(o)
.map(key => {
return `"${key.replace(/"/g, '')}":${this.fixBadJson(o[key])}`;
})
.join(',')
// eslint-disable-next-line @typescript-eslint/indent
}}`;
}

}
4 changes: 4 additions & 0 deletions bsc-plugin/src/lib/rooibos/TestGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export class TestGroup extends TestBlock {
} else if (testCase.isSolo) {
this.hasSoloTests = true;
this.soloTestCases.push(testCase);
this.hasAsyncTests = testCase.isAsync;
} else {
this.hasAsyncTests = testCase.isAsync;
}

}

public getTestCases(): TestCase[] {
Expand Down
12 changes: 12 additions & 0 deletions bsc-plugin/src/lib/rooibos/TestSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export class TestBlock {
public get isSolo(): boolean {
return this.annotation.isSolo;
}
public get isAsync(): boolean {
return this.annotation.isAsync;
}
public get asyncTimeout(): number {
return this.annotation.asyncTimeout;
}

public get isIgnored(): boolean {
return this.annotation.isIgnore;
Expand All @@ -44,6 +50,7 @@ export class TestBlock {

public hasFailures = false;
public hasSoloTests = false;
public hasAsyncTests = false;
public hasIgnoredTests = false;

public setupFunctionName: string;
Expand Down Expand Up @@ -82,6 +89,9 @@ export class TestSuite extends TestBlock {
if (group.hasSoloTests) {
this.hasSoloTests = true;
}
if (group.hasAsyncTests) {
this.annotation.isAsync = true;
}
if (group.isSolo) {
this.hasSoloGroups = true;
}
Expand Down Expand Up @@ -130,6 +140,8 @@ export class TestSuite extends TestBlock {
beforeEachFunctionName: "${this.beforeEachFunctionName || ''}"
afterEachFunctionName: "${this.afterEachFunctionName || ''}"
isNodeTest: ${this.isNodeTest || false}
isAsync: ${this.isAsync || false}
asyncTimeout: ${this.asyncTimeout || 60000}
nodeName: "${this.nodeName || ''}"
generatedNodeName: "${this.generatedNodeName || ''}"
testGroups: [${testGroups}]
Expand Down
56 changes: 56 additions & 0 deletions bsc-plugin/src/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,58 @@ describe('RooibosPlugin', () => {
expect(suite.isSolo).to.be.true;
});

it('finds a @async', () => {
program.setFile('source/test.spec.bs', `
@async
@suite()
class ATest
@describe("groupA")
@async
@it("is test1")
function Test()
end function
end class
`);
program.validate();
expect(program.getDiagnostics()).to.be.empty;
expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
let suite = plugin.session.sessionInfo.testSuitesToRun[0];
expect(suite.name).to.equal('ATest');
expect(suite.isAsync).to.be.true;
expect(suite.asyncTimeout).to.equal(60000);
let test = suite.testGroups.get('groupA').testCases.get('is test1');
expect(test.isAsync).to.be.true;
expect(test.asyncTimeout).to.equal(2000);
});

it('finds a @async and applies timeout override', () => {
program.setFile('source/test.spec.bs', `
@async(1)
@suite("named")
class ATest
@describe("groupA")
@async(2)
@it("is test1")
function Test()
end function
end class
`);
program.validate();
expect(program.getDiagnostics()).to.be.empty;
expect(plugin.session.sessionInfo.testSuitesToRun).to.not.be.empty;
let suite = plugin.session.sessionInfo.testSuitesToRun[0];
expect(suite.name).to.equal('named');
expect(suite.isAsync).to.be.true;
expect(suite.asyncTimeout).to.equal(1);
let test = suite.testGroups.get('groupA').testCases.get('is test1');
expect(test.isAsync).to.be.true;
expect(test.asyncTimeout).to.equal(2);
});

it('ignores a suite', () => {
program.setFile('source/test.spec.bs', `
@ignore
Expand Down Expand Up @@ -367,6 +419,8 @@ describe('RooibosPlugin', () => {
beforeEachFunctionName: ""
afterEachFunctionName: ""
isNodeTest: false
isAsync: false
asyncTimeout: 60000
nodeName: ""
generatedNodeName: "ATest"
testGroups: [
Expand All @@ -386,6 +440,8 @@ describe('RooibosPlugin', () => {
noCatch: false
funcName: "groupA_is_test1"
isIgnored: false
isAsync: false
asyncTimeout: 2000
isParamTest: false
name: "is test1"
lineNumber: 7
Expand Down
Loading

0 comments on commit c33ac17

Please sign in to comment.