Skip to content

Commit

Permalink
test/gopls: add tests covering stretchr test suites
Browse files Browse the repository at this point in the history
  • Loading branch information
Cr4zySheep committed Jan 28, 2024
1 parent e79bcf8 commit 306b9e5
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 28 deletions.
19 changes: 10 additions & 9 deletions extension/src/goTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import {
getBenchmarkFunctions,
getTestFlags,
getTestFunctionDebugArgs,
getSuiteToTestMap,
getTestFunctions,
getTestFunctionsAndTestSuite,
getTestTags,
goTest,
TestConfig,
SuiteToTestMap
SuiteToTestMap,
getTestFunctions
} from './testUtils';

// lastTestConfig holds a reference to the last executed TestConfig which allows
Expand Down Expand Up @@ -54,9 +54,11 @@ async function _testAtCursor(
throw new NotFoundError('No tests found. Current file is not a test file.');
}

const getFunctions = cmd === 'benchmark' ? getBenchmarkFunctions : getTestFunctions;
const testFunctions = (await getFunctions(goCtx, editor.document)) ?? [];
const suiteToTest = await getSuiteToTestMap(goCtx, editor.document);
const { testFunctions, suiteToTest } = await getTestFunctionsAndTestSuite(
cmd === 'benchmark',
goCtx,
editor.document
);
// We use functionName if it was provided as argument
// Otherwise find any test function containing the cursor.
const testFunctionName =
Expand Down Expand Up @@ -95,8 +97,7 @@ async function _subTestAtCursor(
}

await editor.document.save();
const testFunctions = (await getTestFunctions(goCtx, editor.document)) ?? [];
const suiteToTest = await getSuiteToTestMap(goCtx, editor.document);
const { testFunctions, suiteToTest } = await getTestFunctionsAndTestSuite(false, goCtx, editor.document);
// We use functionName if it was provided as argument
// Otherwise find any test function containing the cursor.
const currentTestFunctions = testFunctions.filter((func) => func.range.contains(editor.selection.start));
Expand Down Expand Up @@ -164,7 +165,7 @@ async function _subTestAtCursor(
export function testAtCursor(cmd: TestAtCursorCmd): CommandFactory {
return (ctx, goCtx) => (args: any) => {
const goConfig = getGoConfig();
_testAtCursor(goCtx, goConfig, cmd, args).catch((err) => {
return _testAtCursor(goCtx, goConfig, cmd, args).catch((err) => {
if (err instanceof NotFoundError) {
vscode.window.showInformationMessage(err.message);
} else {
Expand Down
17 changes: 6 additions & 11 deletions extension/src/goTest/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,7 @@ import vscode = require('vscode');
import { outputChannel } from '../goStatus';
import { isModSupported } from '../goModules';
import { getGoConfig } from '../config';
import {
getBenchmarkFunctions,
getTestFlags,
getSuiteToTestMap,
getTestFunctions,
goTest,
GoTestOutput
} from '../testUtils';
import { getTestFlags, getTestFunctionsAndTestSuite, goTest, GoTestOutput } from '../testUtils';
import { GoTestResolver } from './resolve';
import { dispose, forEachAsync, GoTest, Workspace } from './utils';
import { GoTestProfiler, ProfilingOptions } from './profile';
Expand Down Expand Up @@ -168,9 +161,11 @@ export class GoTestRunner {
await doc.save();

const goConfig = getGoConfig(test.uri);
const getFunctions = kind === 'benchmark' ? getBenchmarkFunctions : getTestFunctions;
const testFunctions = await getFunctions(this.goCtx, doc, token);
const suiteToTest = await getSuiteToTestMap(this.goCtx, doc, token);
const { testFunctions, suiteToTest } = await getTestFunctionsAndTestSuite(
kind === 'benchmark',
this.goCtx,
doc
);

// TODO Can we get output from the debug session, in order to check for
// run/pass/fail events?
Expand Down
59 changes: 54 additions & 5 deletions extension/src/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,27 +155,76 @@ export async function getTestFunctions(
doc: vscode.TextDocument,
token?: vscode.CancellationToken
): Promise<vscode.DocumentSymbol[] | undefined> {
const result = await getTestFunctionsAndTestifyHint(goCtx, doc, token);
return result.testFunctions;
}

/**
* Returns all Go unit test functions in the given source file and an hint if testify is used.
*
* @param doc A Go source file
*/
export async function getTestFunctionsAndTestifyHint(
goCtx: GoExtensionContext,
doc: vscode.TextDocument,
token?: vscode.CancellationToken
): Promise<{ testFunctions?: vscode.DocumentSymbol[]; foundTestifyTestFunction?: boolean }> {
const documentSymbolProvider = GoDocumentSymbolProvider(goCtx, true);
const symbols = await documentSymbolProvider.provideDocumentSymbols(doc);
if (!symbols || symbols.length === 0) {
return;
return {};
}
const symbol = symbols[0];
if (!symbol) {
return;
return {};
}
const children = symbol.children;

// With gopls symbol provider, the symbols have the imports of all
// the package, so suite tests from all files will be found.
const testify = importsTestify(symbols);
return children.filter(

const allTestFunctions = children.filter(
(sym) =>
(sym.kind === vscode.SymbolKind.Function || sym.kind === vscode.SymbolKind.Method) &&
sym.kind === vscode.SymbolKind.Function &&
// Skip TestMain(*testing.M) - see https://github.com/golang/vscode-go/issues/482
!testMainRegex.test(doc.lineAt(sym.range.start.line).text) &&
(testFuncRegex.test(sym.name) || fuzzFuncRegx.test(sym.name) || (testify && testMethodRegex.test(sym.name)))
(testFuncRegex.test(sym.name) || fuzzFuncRegx.test(sym.name))
);

const allTestMethods = testify
? children.filter((sym) => sym.kind === vscode.SymbolKind.Method && testMethodRegex.test(sym.name))
: [];

return {
testFunctions: allTestFunctions.concat(allTestMethods),
foundTestifyTestFunction: allTestMethods.length > 0
};
}

/**
* Returns all the Go test functions (or benchmark) from the given Go source file, and the associated test suites when testify is used.
*
* @param doc A Go source file
*/
export async function getTestFunctionsAndTestSuite(
isBenchmark: boolean,
goCtx: GoExtensionContext,
doc: vscode.TextDocument
): Promise<{ testFunctions: vscode.DocumentSymbol[]; suiteToTest: SuiteToTestMap }> {
if (isBenchmark) {
return {
testFunctions: (await getBenchmarkFunctions(goCtx, doc)) ?? [],
suiteToTest: {}
};
}

const { testFunctions, foundTestifyTestFunction } = await getTestFunctionsAndTestifyHint(goCtx, doc);

return {
testFunctions: testFunctions ?? [],
suiteToTest: foundTestifyTestFunction ? await getSuiteToTestMap(goCtx, doc) : {}
};
}

/**
Expand Down
140 changes: 139 additions & 1 deletion extension/test/gopls/codelens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import sinon = require('sinon');
import vscode = require('vscode');
import { updateGoVarsFromConfig } from '../../src/goInstallTools';
import { GoRunTestCodeLensProvider } from '../../src/goRunTestCodelens';
import { subTestAtCursor } from '../../src/goTest';
import { subTestAtCursor, testAtCursor } from '../../src/goTest';
import { MockExtensionContext } from '../mocks/MockContext';
import { Env } from './goplsTestEnv.utils';
import * as testUtils from '../../src/testUtils';

suite('Code lenses for testing and benchmarking', function () {
this.timeout(20000);
Expand Down Expand Up @@ -200,4 +201,141 @@ suite('Code lenses for testing and benchmarking', function () {
// Results should match `go test -list`.
assert.deepStrictEqual(found, ['TestNotMain']);
});

test('Debug - debugs a test with cursor on t.Run line', async () => {
const startDebuggingStub = sinon.stub(vscode.debug, 'startDebugging').returns(Promise.resolve(true));

const editor = await vscode.window.showTextDocument(document);
editor.selection = new vscode.Selection(7, 4, 7, 4);
const result = await subTestAtCursor('debug')(ctx, env.goCtx)([]);
assert.strictEqual(result, true);

assert.strictEqual(startDebuggingStub.callCount, 1, 'expected one call to startDebugging');
const gotConfig = startDebuggingStub.getCall(0).args[1] as vscode.DebugConfiguration;
gotConfig.program = '';
assert.deepStrictEqual<vscode.DebugConfiguration>(gotConfig, {
name: 'Debug Test',
type: 'go',
request: 'launch',
args: ['-test.run', '^TestSample$/^sample_test_passing$'],
buildFlags: '',
env: {},
sessionID: undefined,
mode: 'test',
envFile: null,
program: ''
});
});
});

suite('Code lenses with stretchr/testify/suite', function () {
const ctx = MockExtensionContext.new();

const testdataDir = path.join(__dirname, '..', '..', '..', 'test', 'testdata', 'stretchrTestSuite');
const env = new Env();

this.afterEach(async function () {
// Note: this shouldn't use () => {...}. Arrow functions do not have 'this'.
// I don't know why but this.currentTest.state does not have the expected value when
// used with teardown.
env.flushTrace(this.currentTest?.state === 'failed');
ctx.teardown();
sinon.restore();
});

suiteSetup(async () => {
await updateGoVarsFromConfig({});
await env.startGopls(undefined, undefined, testdataDir);
});

suiteTeardown(async () => {
await env.teardown();
});

test('Run test at cursor', async () => {
const goTestStub = sinon.stub(testUtils, 'goTest').returns(Promise.resolve(true));

const editor = await vscode.window.showTextDocument(vscode.Uri.file(path.join(testdataDir, 'suite_test.go')));
editor.selection = new vscode.Selection(25, 4, 25, 4);

const result = await testAtCursor('test')(ctx, env.goCtx)([]);
assert.strictEqual(result, true);

assert.strictEqual(goTestStub.callCount, 1, 'expected one call to goTest');
const gotConfig = goTestStub.getCall(0).args[0];
assert.deepStrictEqual(gotConfig.functions, ['(*ExampleTestSuite).TestExample', 'TestExampleTestSuite']);
});

test('Run test at cursor in different file than test suite definition', async () => {
const goTestStub = sinon.stub(testUtils, 'goTest').returns(Promise.resolve(true));

const editor = await vscode.window.showTextDocument(
vscode.Uri.file(path.join(testdataDir, 'another_suite_test.go'))
);
editor.selection = new vscode.Selection(3, 4, 3, 4);

const result = await testAtCursor('test')(ctx, env.goCtx)([]);
assert.strictEqual(result, true);

assert.strictEqual(goTestStub.callCount, 1, 'expected one call to goTest');
const gotConfig = goTestStub.getCall(0).args[0];
assert.deepStrictEqual(gotConfig.functions, [
'(*ExampleTestSuite).TestExampleInAnotherFile',
'TestExampleTestSuite'
]);
});

test('Debug test at cursor', async () => {
const startDebuggingStub = sinon.stub(vscode.debug, 'startDebugging').returns(Promise.resolve(true));

const editor = await vscode.window.showTextDocument(vscode.Uri.file(path.join(testdataDir, 'suite_test.go')));
editor.selection = new vscode.Selection(25, 4, 25, 4);

const result = await testAtCursor('debug')(ctx, env.goCtx)([]);
assert.strictEqual(result, true);

assert.strictEqual(startDebuggingStub.callCount, 1, 'expected one call to startDebugging');
const gotConfig = startDebuggingStub.getCall(0).args[1] as vscode.DebugConfiguration;
gotConfig.program = '';
assert.deepStrictEqual<vscode.DebugConfiguration>(gotConfig, {
name: 'Debug Test',
type: 'go',
request: 'launch',
args: ['-test.run', '^TestExampleTestSuite$', '-testify.m', '^TestExample$'],
buildFlags: '',
env: {},
sessionID: undefined,
mode: 'test',
envFile: null,
program: ''
});
});

test('Debug test at cursor in different file than test suite definition', async () => {
const startDebuggingStub = sinon.stub(vscode.debug, 'startDebugging').returns(Promise.resolve(true));

const editor = await vscode.window.showTextDocument(
vscode.Uri.file(path.join(testdataDir, 'another_suite_test.go'))
);
editor.selection = new vscode.Selection(3, 4, 3, 4);

const result = await testAtCursor('debug')(ctx, env.goCtx)([]);
assert.strictEqual(result, true);

assert.strictEqual(startDebuggingStub.callCount, 1, 'expected one call to startDebugging');
const gotConfig = startDebuggingStub.getCall(0).args[1] as vscode.DebugConfiguration;
gotConfig.program = '';
assert.deepStrictEqual<vscode.DebugConfiguration>(gotConfig, {
name: 'Debug Test',
type: 'go',
request: 'launch',
args: ['-test.run', '^TestExampleTestSuite$', '-testify.m', '^TestExampleInAnotherFile$'],
buildFlags: '',
env: {},
sessionID: undefined,
mode: 'test',
envFile: null,
program: ''
});
});
});
5 changes: 5 additions & 0 deletions extension/test/gopls/goTest.run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ import { GoTestExplorer } from '../../src/goTest/explore';
import { MockExtensionContext } from '../mocks/MockContext';
import { GoTest } from '../../src/goTest/utils';
import { Env } from './goplsTestEnv.utils';
import { updateGoVarsFromConfig } from '../../src/goInstallTools';

suite('Go Test Runner', () => {
const fixtureDir = path.join(__dirname, '..', '..', '..', 'test', 'testdata');

let testExplorer: GoTestExplorer;

suiteSetup(async () => {
await updateGoVarsFromConfig({});
});

suite('parseOutput', () => {
const ctx = MockExtensionContext.new();
suiteSetup(async () => {
Expand Down
13 changes: 13 additions & 0 deletions extension/test/integration/test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ suite('Test Go Test Args', () => {
flags: ['-run', 'TestC']
});
});
test('use -testify.m for methods', () => {
runTest({
expectedArgs:
'test -timeout 30s -run ^TestExampleTestSuite$ -testify.m ^(TestExample|TestAnotherExample)$ ./...',
expectedOutArgs:
'test -timeout 30s -run ^TestExampleTestSuite$ -testify.m ^(TestExample|TestAnotherExample)$ ./...',
functions: [
'(*ExampleTestSuite).TestExample',
'(*ExampleTestSuite).TestAnotherExample',
'TestExampleTestSuite'
]
});
});
});

suite('Test Go Test', function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main_test

func (suite *ExampleTestSuite) TestExampleInAnotherFile() {
if suite.VariableThatShouldStartAtFive != 5 {
suite.T().Fatalf("%d != %d", 5, suite.VariableThatShouldStartAtFive)
}
}
7 changes: 5 additions & 2 deletions extension/test/testdata/stretchrTestSuite/go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
module example/a

go 1.16
go 1.21

require github.com/stretchr/testify v1.7.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/stretchr/testify v1.7.0
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

0 comments on commit 306b9e5

Please sign in to comment.