Skip to content

Commit

Permalink
Merge pull request #3 from mnoah1/uber-release-additional-commits
Browse files Browse the repository at this point in the history
Clean up testify suite logic and support nested mode with Bazel
  • Loading branch information
mnoah1 authored Jan 9, 2024
2 parents f823e41 + 8cf045c commit 6e95fb2
Show file tree
Hide file tree
Showing 12 changed files with 491 additions and 12 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## v0.1.10 - 21 Dec, 2023
- Updates to fix nested mode when using Bazel, and index all tests under a given path

## v0.1.9 - 6 Oct, 2023
- Bug fix: Ensure that test explorer cleans up old test case names in Testify suites

## v0.1.8 - 16 Aug, 2023
- Internal use

## v0.1.7 - 10 May, 2023
- Show duplciate extension warning when the user has the regular "Go" extension installed.
- Bug fixes
Expand Down
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "go-bazel",
"displayName": "Go with Bazel",
"version": "0.39.1-upstream-0.1.7-uber",
"version": "0.39.1-upstream-0.1.10-uber",
"preview": false,
"publisher": "Uber",
"description": "Rich Go language support for Visual Studio Code. Supports test and debug with Bazel",
Expand Down Expand Up @@ -379,6 +379,12 @@
"description": "Run a single test with coverage enabled.",
"icon": "$(debug-coverage)"
},
{
"command": "go.test.getAllChildren",
"title": "Resolve All Tests Under this Package",
"description": "Resolve all test cases below this path.",
"icon": "$(debug-coverage)"
},
{
"command": "go.test.generate.package",
"title": "Go: Generate Unit Tests For Package",
Expand Down Expand Up @@ -2899,6 +2905,12 @@
"group": "inline",
"icon": "$(debug-coverage)"
},
{
"command": "go.test.getAllChildren",
"when": "testId in go.tests",
"group": "resolve",
"icon": "$(debug-coverage)"
},
{
"command": "go.test.showProfiles",
"when": "testId in go.profiledTests",
Expand Down
17 changes: 16 additions & 1 deletion src/bazel/bazelExplore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,22 @@ export class BazelGoTestExplorer extends GoTestExplorer {
}
})
);

vscode.commands.registerCommand('go.test.getAllChildren', async (item) => {
if (!item) {
await vscode.window.showErrorMessage('No test selected');
return;
}

try {
await this.resolver.getAllTestsUnderDirectory(item);
this.resolver.updateGoTestContext();
} catch (error) {
const m = 'Failed to resolve tests';
outputChannel.appendLine(`${m}: ${error}`);
outputChannel.show();
await vscode.window.showErrorMessage(m);
}
});
this.extensionCtx.subscriptions.push(
workspace.onDidOpenTextDocument(async (x) => {
try {
Expand Down
7 changes: 4 additions & 3 deletions src/bazel/bazelTestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export async function goTestWithBazel(

// compute test target package and generate full args.
const { targets, currentGoWorkspace } = await getTestTargetPackages(testconfig, outputChannel);
const bazelTargets = await getBazelTargetsFromPackages(testconfig, targets, currentGoWorkspace, debugConfig);
const currentWorkingDirectory = currentGoWorkspace ? currentGoWorkspace : testconfig.dir; // Run from test location if worskpace not found
const bazelTargets = await getBazelTargetsFromPackages(testconfig, targets, currentWorkingDirectory, debugConfig);
const buildEventsFile = path.join(os.tmpdir(), `build_events_${hash(outputChannel.name)}`); // Output channel name provides unique timestamp for temp file.
const bazelArgs = getBazelArgs(testconfig, bazelTargets, buildEventsFile, debugConfig);

Expand All @@ -108,7 +109,7 @@ export async function goTestWithBazel(
try {
testResult = await new Promise<RunResult>((resolve) => {
// TODO: Add support of test environment variables. IDE-73
const testProcess = cp.exec(['bazel', ...bazelArgs].join(' '), { cwd: currentGoWorkspace });
const testProcess = cp.exec(['bazel', ...bazelArgs].join(' '), { cwd: currentWorkingDirectory });
const outBuf = new LineBuffer();
const errBuf = new LineBuffer();

Expand Down Expand Up @@ -162,7 +163,7 @@ export async function goTestWithBazel(
'/bazel-out/_coverage/_coverage_report.dat'
),
bazelWorkspaceRoot: buildEventOutputs.workspaceDirectory,
currentGoWorkspace: currentGoWorkspace,
currentGoWorkspace: currentWorkingDirectory,
targetPackages: targets,
generatedFilePrefix: buildEventOutputs.genDir || ''
});
Expand Down
42 changes: 38 additions & 4 deletions src/goTest/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ export class GoTestResolver {
return it(this.items);
}

// load packages and tests under this directory as test items in the test explorer
async getAllTestsUnderDirectory(item: TestItem) {
if (!item.uri) return;
await walkPackages(this.workspace.fs, item.uri, async (uri) => {
await this.getPackage(uri);
});
}

// Create or Retrieve a sub test or benchmark. The ID will be of the form:
// file:///path/to/mod/file.go?test#TestXxx%2fA%2fB%2fC
getOrCreateSubTest(item: TestItem, label: string, name: string, dynamic?: boolean): TestItem | undefined {
Expand Down Expand Up @@ -213,6 +221,7 @@ export class GoTestResolver {
await this.processSymbol(doc, item, seen, testify, symbol);
}

if (testify) this.cleanupTestSuites(item, seen);
item.children.forEach((child) => {
const { name } = GoTest.parseId(child.id);
if (!name || !seen.has(name)) {
Expand Down Expand Up @@ -338,10 +347,18 @@ export class GoTestResolver {
const nested = getGoConfig(uri).get('testExplorer.packageDisplayMode') === 'nested';
const modDirPath = await getModFolderPath(uri, true);
const wsfolder = workspace.getWorkspaceFolder(uri);
if (modDirPath) {
const modDir = Uri.file(modDirPath); // TODO support non-file schemes
if (modDirPath || nested) {
// If the package is in a module, add it as a child of the module
let parent = await this.getModule(modDir);
let parent: TestItem | undefined = undefined;
if (modDirPath) {
const modDir = Uri.file(modDirPath); // TODO support non-file schemes
parent = await this.getModule(modDir);
} else if (nested && wsfolder !== undefined) {
parent = await this.getWorkspace(wsfolder);
} else {
return;
}

if (uri.path === parent.uri?.path) {
return parent;
}
Expand Down Expand Up @@ -491,7 +508,6 @@ export class GoTestResolver {

const suite = this.getTestSuite(g?.type1 || g?.type2 || variableDef?.groups?.type3 || '');
suite.func = item;

for (const method of suite.methods) {
if (!method.parent || GoTest.parseId(method.parent.id).kind !== 'file') {
continue;
Expand All @@ -516,6 +532,24 @@ export class GoTestResolver {
item.range.isSingleLine ? item.range.end : new vscode.Position(item.range.start.line, 200)
);
}

/**
* Clean up a suite by removing test cases that are no longer seen in a given file.
* @param file TestItem representing the file to be checked, since suites can be spread across multiple files.
* @param seen Set of test cases that were seen in the given file during the current update.
*/
private cleanupTestSuites(file: TestItem, seen: Set<string>) {
for (const currentSuite of this.testSuites.values()) {
currentSuite.func?.children.forEach((suiteMethod) => {
const { name } = GoTest.parseId(suiteMethod.id);
// Only dispose of the test case if it is in the same file.
// We want to avoid disposing of test cases that were not seen because they are in a different file.
if (name && suiteMethod.uri?.fsPath === file.uri?.fsPath && !seen.has(name)) {
dispose(this, suiteMethod);
}
});
}
}
}

// Walk the workspace, looking for Go modules. Returns a map indicating paths
Expand Down
113 changes: 112 additions & 1 deletion test/integration/bazel.bazelRun.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
TestController,
TestRun,
OutputChannel,
TestItem
TestItem,
TestItemCollection
} from 'vscode';
import * as bazelTestUtils from '../../src/bazel/bazelTestUtils';
import * as testUtils from '../../src/testUtils';
Expand All @@ -25,6 +26,8 @@ import { GoTestResolver } from '../../src/goTest/resolve';
import { GoTestRunner, TestRunOutput } from '../../src/goTest/run';
import { MockTestController } from '../mocks/MockTest';
import { BazelGoTestRunner } from '../../src/bazel/bazelRun';
import * as config from '../../src/config';
import * as goModules from '../../src/goModules';

// TODO:
// - Tests for bazelDebugTestAtCursor()
Expand Down Expand Up @@ -164,6 +167,114 @@ suite('Bazel Go Test Runner', () => {
)
);
});

suite('Resolve all child packages of a package', async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let goConfig: any;

setup(() => {
goConfig = Object.create(config.getGoConfig());
sandbox.stub(config, 'getGoConfig').returns(goConfig);
});

const childrenToArray = (children: TestItemCollection) => {
const arr: TestItem[] = [];
children.forEach((child) => arr.push(child));
return arr;
};
const getPackageDisplayStub = sinon.stub(goConfig, 'get');
const getModFolderPathStub = sinon.stub(goModules, 'getModFolderPath');

test('Resolve subtests of a given directory with nested display', async () => {
setup(() => {
getPackageDisplayStub.withArgs('testExplorer.packageDisplayMode').returns('nested');
getModFolderPathStub.returns(Promise.resolve(undefined));
});
teardown(() => {
getModFolderPathStub.reset();
getPackageDisplayStub.reset();
});
const ctrl = new MockTestController();
const item = ctrl.createTestItem(
'',
'',
Uri.file(path.join(__dirname, '../../../test/testdata/getAllPackagesTest/A'))
);

await testExplorer.resolver.getAllTestsUnderDirectory(item);

const items = testExplorer.resolver.find(item.uri!);
// ensure the directory structure is correct
// should create two items (A and A/AA) in this nested case
assert.ok(items.length === 1);
const rootPackageItem = items[0];
assert.ok(rootPackageItem.id.includes('A') === true);
assert.ok(rootPackageItem.id.includes('A/AA') === false);
assert.ok(rootPackageItem.children.size === 1);
const innerPackageItem = childrenToArray(rootPackageItem.children)[0];
assert.ok(innerPackageItem.id.includes('AA') === true);
assert.ok(innerPackageItem.children.size === 0);
});

test('Resolve subtests of a given directory with nested display using modFolderPath', async () => {
setup(() => {
getPackageDisplayStub.withArgs('testExplorer.packageDisplayMode').returns('nested');
getModFolderPathStub.returns(Promise.resolve(path.join(__dirname, '../../..')));
});
teardown(() => {
getModFolderPathStub.reset();
getPackageDisplayStub.reset();
});
const ctrl = new MockTestController();
const item = ctrl.createTestItem(
'',
'',
Uri.file(path.join(__dirname, '../../../test/testdata/getAllPackagesTest/A'))
);

await testExplorer.resolver.getAllTestsUnderDirectory(item);

const items = testExplorer.resolver.find(item.uri!);
// ensure the directory structure is correct
// should create two items (A and A/AA) in this nested case
assert.ok(items.length === 1);
const rootPackageItem = items[0];
assert.ok(rootPackageItem.id.includes('A') === true);
assert.ok(rootPackageItem.id.includes('A/AA') === false);
assert.ok(rootPackageItem.children.size === 1);
const innerPackageItem = childrenToArray(rootPackageItem.children)[0];
assert.ok(innerPackageItem.id.includes('AA') === true);
assert.ok(innerPackageItem.children.size === 0);
});

test('Resolve subtests of a given directory with flat display using modFolderPath', async () => {
setup(() => {
getPackageDisplayStub.withArgs('testExplorer.packageDisplayMode').returns('flat');
getModFolderPathStub.returns(Promise.resolve(path.join(__dirname, '../../..')));
});
teardown(() => {
getModFolderPathStub.reset();
getPackageDisplayStub.reset();
});
getPackageDisplayStub.withArgs('testExplorer.packageDisplayMode').returns('flat');
const ctrl = new MockTestController();
const item = ctrl.createTestItem(
'',
'',
Uri.file(path.join(__dirname, '../../../test/testdata/getAllPackagesTest/A'))
);
const expectedRootPackageUri = Uri.file(
path.join(__dirname, '../../../test/testdata/getAllPackagesTest/A/AA')
);
await testExplorer.resolver.getAllTestsUnderDirectory(item);
const rootPackageItem = testExplorer.resolver.find(expectedRootPackageUri)[0];
// ensure the directory structure is correct
// should only create a single item in this flat case
assert.ok(rootPackageItem !== undefined);
assert.ok(rootPackageItem.id.includes('A/AA') === true);
assert.ok(rootPackageItem.children.size === 0);
});
});
});
});

Expand Down
Loading

0 comments on commit 6e95fb2

Please sign in to comment.