testing: show stats about the last test run

This commit is contained in:
Connor Peet
2021-01-20 16:30:25 -08:00
parent aa14d823df
commit 0e9e4e4677
6 changed files with 101 additions and 26 deletions
@@ -197,6 +197,10 @@ export class ExtHostTesting implements ExtHostTestingShape {
try {
await provider.runTests({ tests, debug: req.debug }, cancellation);
for (const { collection } of this.testSubscriptions.values()) {
collection.flushDiff(); // ensure all states are updated
}
return EMPTY_TEST_RESULT;
} catch (e) {
console.error(e); // so it appears to attached debuggers
@@ -46,6 +46,10 @@
margin-right: 0.25em;
}
.test-explorer .test-explorer-messages {
padding: 0 12px 8px;
}
.monaco-workbench
.test-explorer
.monaco-action-bar
@@ -63,11 +67,11 @@
}
/** -- peek */
.monaco-editor .zone-widget .zone-widget-container.peekview-widget {
.monaco-editor .zone-widget.test-output-peek .zone-widget-container.peekview-widget {
border-top-width: 2px;
border-bottom-width: 2px;
}
/* .monaco-editor .zone-widget .zone-widget-container.peekview-widget .peekview-title .filename {
height: 22px;
} */
.monaco-editor .zone-widget.test-output-peek .zone-widget-container.peekview-widget .peekview-title .filename {
max-height: 29px;
}
@@ -11,6 +11,7 @@ import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTre
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { DeferredPromise } from 'vs/base/common/async';
import { Color, RGBA } from 'vs/base/common/color';
import { throttle } from 'vs/base/common/decorators';
import { Event } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
@@ -34,7 +35,8 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProgress, IProgressService, IProgressStep } from 'vs/platform/progress/common/progress';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { foreground } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from 'vs/workbench/browser/labels';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
@@ -53,7 +55,7 @@ import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browse
import { TestExplorerViewGrouping, TestExplorerViewMode, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { ITestResultService, sumCounts } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestResultService, sumCounts, TestStateCount } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { IWorkspaceTestCollectionService, TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
import { IActivityService, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
@@ -104,12 +106,13 @@ export class TestingExplorerView extends ViewPane {
this.filter = this.instantiationService.createInstance(TestingExplorerFilter, this.container, this.filterState);
this._register(this.filter);
const messagesContainer = dom.append(this.container, dom.$('.test-explorer-messages'));
this._register(this.instantiationService.createInstance(TestRunProgress, messagesContainer, this.getProgressLocation()));
const listContainer = dom.append(this.container, dom.$('.test-explorer-tree'));
this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription, this.filterState);
this._register(this.viewModel);
this._register(this.instantiationService.createInstance(TestRunProgress, this.getProgressLocation()));
this._register(this.onDidChangeBodyVisibility(visible => {
if (!visible && this.currentSubscription) {
this.currentSubscription.dispose();
@@ -651,6 +654,29 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
}
}
const collectCounts = (count: TestStateCount) => {
const failed = count[TestRunState.Errored] + count[TestRunState.Failed];
const passed = count[TestRunState.Passed];
const skipped = count[TestRunState.Skipped];
return {
passed,
failed,
runSoFar: passed + failed,
totalWillBeRun: passed + failed + count[TestRunState.Queued] + count[TestRunState.Running],
skipped,
};
};
const getProgressText = ({ passed, runSoFar, skipped }: { passed: number, runSoFar: number, skipped: number }) => {
const percent = (passed / runSoFar * 100).toFixed(0);
if (skipped === 0) {
return localize('testProgress', '{0}/{1} tests passed ({2}%)', passed, runSoFar, percent);
} else {
return localize('testProgressWithSkip', '{0}/{1} tests passed ({2}%, {3} skipped)', passed, runSoFar, percent, skipped);
}
};
class TestRunProgress {
private current?: { update: IProgress<IProgressStep>; deferred: DeferredPromise<void> };
private badge = new MutableDisposable();
@@ -666,11 +692,13 @@ class TestRunProgress {
});
constructor(
private readonly messagesContainer: HTMLElement,
private readonly location: string,
@IProgressService private readonly progress: IProgressService,
@ITestResultService private readonly resultService: ITestResultService,
@IActivityService private readonly activityService: IActivityService,
) { }
) {
}
public dispose() {
this.resultLister.dispose();
@@ -686,6 +714,7 @@ class TestRunProgress {
private updateProgress() {
const running = this.resultService.results.filter(r => !r.isComplete);
if (!running.length) {
this.setIdleText(this.resultService.results[0]?.counts);
this.current?.deferred.complete();
this.current = undefined;
} else if (!this.current) {
@@ -695,15 +724,36 @@ class TestRunProgress {
return this.current.deferred.p;
});
} else {
const count = sumCounts(running.map(r => r.counts));
const completed = count[TestRunState.Errored] + count[TestRunState.Failed] + count[TestRunState.Passed];
const total = completed + count[TestRunState.Queued] + count[TestRunState.Running];
this.current.update.report({ increment: completed, total });
const counts = sumCounts(running.map(r => r.counts));
this.setRunningText(counts);
const { runSoFar, totalWillBeRun } = collectCounts(counts);
this.current.update.report({ increment: runSoFar, total: totalWillBeRun });
}
}
private setRunningText(counts: TestStateCount) {
this.messagesContainer.dataset.state = 'running';
const collected = collectCounts(counts);
if (collected.runSoFar === 0) {
this.messagesContainer.innerText = localize('testResultStarting', 'Test run is starting...');
} else {
this.messagesContainer.innerText = getProgressText(collected);
}
}
private setIdleText(lastCount?: TestStateCount) {
if (!lastCount) {
this.messagesContainer.innerText = '';
} else {
const collected = collectCounts(lastCount);
this.messagesContainer.dataset.state = collected.failed ? 'failed' : 'running';
this.messagesContainer.innerText = getProgressText(collected);
}
}
private updateBadge() {
this.badge.dispose();
this.badge.value = undefined;
const result = this.resultService.results[0]; // currently running, or last run
if (!result) {
return;
@@ -711,7 +761,7 @@ class TestRunProgress {
if (!result.isComplete) {
const badge = new ProgressBadge(() => localize('testBadgeRunning', 'Test run in progress'));
this.badge.value = this.activityService.showViewActivity(Testing.ExplorerViewId, { badge });
this.badge.value = this.activityService.showViewActivity(Testing.ExplorerViewId, { badge, clazz: 'progress-badge' });
return;
}
@@ -724,3 +774,13 @@ class TestRunProgress {
this.badge.value = this.activityService.showViewActivity(Testing.ExplorerViewId, { badge });
}
}
registerThemingParticipant((theme, collector) => {
if (theme.type === 'dark') {
const foregroundColor = theme.getColor(foreground);
if (foregroundColor) {
const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.65));
collector.addRule(`.test-explorer .test-explorer-messages { color: ${fgWithOpacity}; }`);
}
}
});
@@ -102,7 +102,7 @@ export class TestingOutputPeek extends PeekViewWidget {
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@ITextModelService private readonly modelService: ITextModelService,
) {
super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }, instantiationService);
super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true, className: 'test-output-peek' }, instantiationService);
this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme, this));
this.applyTheme(themeService.getColorTheme());
@@ -188,6 +188,10 @@ export class SingleUseTestCollection implements IDisposable {
@throttle(200)
protected throttleSendDiff() {
this.flushDiff();
}
public flushDiff() {
const diff = this.collectDiff();
if (diff.length) {
this.publishDiff(diff);
@@ -38,9 +38,7 @@ const makeNode = (
collection: IMainThreadTestCollection,
test: IncrementalTestCollectionItem,
): TestResultItem => {
const mapped: TestResultItem = { ...test, children: [], results: makeEmptyCounts() };
mapped.results[test.item.state.runState]++;
const mapped: TestResultItem = { ...test, children: [] };
for (const childId of test.children) {
const child = collection.getNodeById(childId);
if (child) {
@@ -53,7 +51,6 @@ const makeNode = (
export interface TestResultItem extends InternalTestItem {
children: TestResultItem[]
results: { [K in TestRunState]: number };
}
/**
@@ -191,7 +188,7 @@ export class TestResultService implements ITestResultService {
/**
* @inheritdoc
*/
public readonly results: TestResult[] = [];
public results: TestResult[] = [];
/**
* @inheritdoc
@@ -213,14 +210,20 @@ export class TestResultService implements ITestResultService {
this.results.pop();
}
result.onComplete(this.reorder, this);
this.reorder();
result.onComplete(this.onComplete, this);
this.isRunning.set(true);
this.newResultEmitter.fire(result);
return result;
}
private reorder() {
this.results.sort((a, b) => (a.isComplete ? 0 : 1) - (b.isComplete ? 0 : 1));
this.isRunning.set(this.results.length > 0 && !this.results[0].isComplete);
private onComplete() {
// move the complete test run down behind any still-running ones
for (let i = 0; i < this.results.length - 2; i++) {
if (this.results[i].isComplete && !this.results[i + 1].isComplete) {
[this.results[i], this.results[i + 1]] = [this.results[i + 1], this.results[i]];
}
}
this.isRunning.set(!this.results[0]?.isComplete);
}
}