Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
d1280418
V
vscode
项目概览
xxadev
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
d1280418
编写于
11月 20, 2020
作者:
C
Connor Peet
提交者:
GitHub
11月 20, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
testing: initial api implementation
* wip * wip * wip * wip * wip * wip
上级
ff1887be
变更
16
显示空白变更内容
内联
并排
Showing
16 changed file
with
1732 addition
and
3 deletion
+1732
-3
.eslintrc.json
.eslintrc.json
+1
-0
src/vs/base/common/arrays.ts
src/vs/base/common/arrays.ts
+14
-0
src/vs/vscode.proposed.d.ts
src/vs/vscode.proposed.d.ts
+289
-0
src/vs/workbench/api/browser/extensionHost.contribution.ts
src/vs/workbench/api/browser/extensionHost.contribution.ts
+1
-0
src/vs/workbench/api/browser/mainThreadTesting.ts
src/vs/workbench/api/browser/mainThreadTesting.ts
+77
-0
src/vs/workbench/api/common/extHost.api.impl.ts
src/vs/workbench/api/common/extHost.api.impl.ts
+26
-1
src/vs/workbench/api/common/extHost.protocol.ts
src/vs/workbench/api/common/extHost.protocol.ts
+27
-2
src/vs/workbench/api/common/extHostTesting.ts
src/vs/workbench/api/common/extHostTesting.ts
+623
-0
src/vs/workbench/api/common/extHostTypeConverters.ts
src/vs/workbench/api/common/extHostTypeConverters.ts
+62
-0
src/vs/workbench/api/common/extHostTypes.ts
src/vs/workbench/api/common/extHostTypes.ts
+44
-0
src/vs/workbench/contrib/testing/browser/testing.contribution.ts
...workbench/contrib/testing/browser/testing.contribution.ts
+10
-0
src/vs/workbench/contrib/testing/common/testCollection.ts
src/vs/workbench/contrib/testing/common/testCollection.ts
+197
-0
src/vs/workbench/contrib/testing/common/testService.ts
src/vs/workbench/contrib/testing/common/testService.ts
+30
-0
src/vs/workbench/contrib/testing/common/testServiceImpl.ts
src/vs/workbench/contrib/testing/common/testServiceImpl.ts
+128
-0
src/vs/workbench/test/browser/api/extHostTesting.test.ts
src/vs/workbench/test/browser/api/extHostTesting.test.ts
+200
-0
src/vs/workbench/workbench.common.main.ts
src/vs/workbench/workbench.common.main.ts
+3
-0
未找到文件。
.eslintrc.json
浏览文件 @
d1280418
...
...
@@ -983,6 +983,7 @@
"collapse"
,
"create"
,
"delete"
,
"discover"
,
"dispose"
,
"edit"
,
"end"
,
...
...
src/vs/base/common/arrays.ts
浏览文件 @
d1280418
...
...
@@ -588,3 +588,17 @@ export function asArray<T>(x: T | T[]): T[] {
export
function
getRandomElement
<
T
>
(
arr
:
T
[]):
T
|
undefined
{
return
arr
[
Math
.
floor
(
Math
.
random
()
*
arr
.
length
)];
}
/**
* Returns the first mapped value of the array which is not undefined.
*/
export
function
mapFind
<
T
,
R
>
(
array
:
Iterable
<
T
>
,
mapFn
:
(
value
:
T
)
=>
R
|
undefined
):
R
|
undefined
{
for
(
const
value
of
array
)
{
const
mapped
=
mapFn
(
value
);
if
(
mapped
!==
undefined
)
{
return
mapped
;
}
}
return
undefined
;
}
src/vs/vscode.proposed.d.ts
浏览文件 @
d1280418
...
...
@@ -2149,4 +2149,293 @@ declare module 'vscode' {
notebook
:
NotebookDocument
|
undefined
;
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/107467
/*
General activation events:
- `onLanguage:*` most test extensions will want to activate when their
language is opened to provide code lenses.
- `onTests:*` new activation event very simiular to `workspaceContains`,
but only fired when the user wants to run tests or opens the test explorer.
*/
export
namespace
test
{
/**
* Registers a provider that discovers tests for the given document
* selectors. It is activated when either tests need to be enumerated, or
* a document matching the selector is opened.
*/
export
function
registerTestProvider
<
T
extends
TestItem
>
(
testProvider
:
TestProvider
<
T
>
):
Disposable
;
/**
* Runs tests with the given options. If no options are given, then
* all tests are run. Returns the resulting test run.
*/
export
function
runTests
<
T
extends
TestItem
>
(
options
:
TestRunOptions
<
T
>
):
Thenable
<
void
>
;
/**
* Returns an observer that retrieves tests in the given workspace folder.
*/
export
function
createWorkspaceTestObserver
(
workspaceFolder
:
WorkspaceFolder
):
TestObserver
;
/**
* Returns an observer that retrieves tests in the given text document.
*/
export
function
createDocumentTestObserver
(
document
:
TextDocument
):
TestObserver
;
}
export
interface
TestObserver
{
/**
* List of tests returned by test provider for files in the workspace.
*/
readonly
tests
:
ReadonlyArray
<
TestItem
>
;
/**
* An event that fires when an existing test in the collection changes, or
* null if a top-level test was added or removed. When fired, the consumer
* should check the test item and all its children for changes.
*/
readonly
onDidChangeTest
:
Event
<
TestItem
|
null
>
;
/**
* An event the fires when all test providers have signalled that the tests
* the observer references have been discovered. Providers may continue to
* watch for changes and cause {@link onDidChangeTest} to fire as files
* change, until the observer is disposed.
*
* @todo as below
*/
readonly
onDidDiscoverInitialTests
:
Event
<
void
>
;
/**
* Dispose of the observer, allowing VS Code to eventually tell test
* providers that they no longer need to update tests.
*/
dispose
():
void
;
}
/**
* Tree of tests returned from the provide methods in the {@link TestProvider}.
*/
export
interface
TestHierarchy
<
T
extends
TestItem
>
{
/**
* Root node for tests. The `testRoot` instance must not be replaced over
* the lifespan of the TestHierarchy, since you will need to reference it
* in `onDidChangeTest` when a test is added or removed.
*/
readonly
root
:
T
;
/**
* An event that fires when an existing test under the `root` changes.
* This can be a result of a state change in a test run, a property update,
* or an update to its children. Changes made to tests will not be visible
* to {@link TestObserver} instances until this event is fired.
*
* This will signal a change recursively to all children of the given node.
* For example, firing the event with the {@link testRoot} will refresh
* all tests.
*/
readonly
onDidChangeTest
:
Event
<
T
>
;
/**
* An event that should be fired when all tests that are currently defined
* have been discovered. The provider should continue to watch for changes
* and fire `onDidChangeTest` until the hierarchy is disposed.
*
* @todo can this be covered by existing progress apis? Or return a promise
*/
readonly
onDidDiscoverInitialTests
:
Event
<
void
>
;
/**
* Dispose will be called when there are no longer observers interested
* in the hierarchy.
*/
dispose
():
void
;
}
/**
* Discovers and provides tests. It's expected that the TestProvider will
* ambiently listen to {@link vscode.window.onDidChangeVisibleTextEditors} to
* provide test information about the open files for use in code lenses and
* other file-specific UI.
*
* Additionally, the UI may request it to discover tests for the workspace
* via `addWorkspaceTests`.
*
* @todo rename from provider
*/
export
interface
TestProvider
<
T
extends
TestItem
=
TestItem
>
{
/**
* Requests that tests be provided for the given workspace. This will
* generally be called when tests need to be enumerated for the
* workspace.
*
* It's guaranteed that this method will not be called again while
* there is a previous undisposed watcher for the given workspace folder.
*/
createWorkspaceTestHierarchy
?(
workspace
:
WorkspaceFolder
):
TestHierarchy
<
T
>
;
/**
* Requests that tests be provided for the given document. This will
* be called when tests need to be enumerated for a single open file,
* for instance by code lens UI.
*/
createDocumentTestHierarchy
?(
document
:
TextDocument
):
TestHierarchy
<
T
>
;
/**
* Starts a test run. This should cause {@link onDidChangeTest} to
* fire with update test states during the run.
* @todo this will eventually need to be able to return a summary report, coverage for example.
*/
runTests
?(
options
:
TestRunOptions
<
T
>
,
cancellationToken
:
CancellationToken
):
ProviderResult
<
void
>
;
}
/**
* Options given to `TestProvider.runTests`
*/
export
interface
TestRunOptions
<
T
extends
TestItem
=
TestItem
>
{
/**
* Array of specific tests to run. The {@link TestProvider.testRoot} may
* be provided as an indication to run all tests.
*/
tests
:
T
[];
/**
* Whether or not tests in this run should be debugged.
*/
debug
:
boolean
;
}
/**
* A test item is an item shown in the "test explorer" view. It encompasses
* both a suite and a test, since they have almost or identical capabilities.
*/
export
interface
TestItem
{
/**
* Display name describing the test case.
*/
label
:
string
;
/**
* Optional description that appears next to the label.
*/
description
?:
string
;
/**
* Whether this test item can be run individually, defaults to `true`
* if not provided.
*
* In some cases, like Go's tests, test can have children but these
* children cannot be run independently.
*/
runnable
?:
boolean
;
/**
* Whether this test item can be debugged.
*/
debuggable
?:
boolean
;
/**
* VS Code location.
*/
location
?:
Location
;
/**
* Optional list of nested tests for this item.
*/
children
?:
TestItem
[];
/**
* Test run state. Will generally be {@link TestRunState.Unset} by
* default.
*/
state
:
TestState
;
}
export
enum
TestRunState
{
// Initial state
Unset
=
0
,
// Test is currently running
Running
=
1
,
// Test run has passed
Passed
=
2
,
// Test run has failed (on an assertion)
Failed
=
3
,
// Test run has been skipped
Skipped
=
4
,
// Test run failed for some other reason (compilation error, timeout, etc)
Errored
=
5
}
/**
* TestState includes a test and its run state. This is included in the
* {@link TestItem} and is immutable; it should be replaced in th TestItem
* in order to update it. This allows consumers to quickly and easily check
* for changes via object identity.
*/
export
class
TestState
{
/**
* Current state of the test.
*/
readonly
runState
:
TestRunState
;
/**
* Optional duration of the test run, in milliseconds.
*/
readonly
duration
?:
number
;
/**
* Associated test run message. Can, for example, contain assertion
* failure information if the test fails.
*/
readonly
messages
:
ReadonlyArray
<
Readonly
<
TestMessage
>>
;
/**
* @param state Run state to hold in the test state
* @param messages List of associated messages for the test
* @param duration Length of time the test run took, if appropriate.
*/
constructor
(
runState
:
TestRunState
,
messages
?:
TestMessage
[],
duration
?:
number
);
}
/**
* Represents the severity of test messages.
*/
export
enum
TestMessageSeverity
{
Error
=
0
,
Warning
=
1
,
Information
=
2
,
Hint
=
3
}
/**
* Message associated with the test state. Can be linked to a specific
* source range -- useful for assertion failures, for example.
*/
export
interface
TestMessage
{
/**
* Human-readable message text to display.
*/
message
:
string
|
MarkdownString
;
/**
* Message severity. Defaults to "Error", if not provided.
*/
severity
?:
TestMessageSeverity
;
/**
* Expected test output. If given with `actual`, a diff view will be shown.
*/
expectedOutput
?:
string
;
/**
* Actual test output. If given with `actual`, a diff view will be shown.
*/
actualOutput
?:
string
;
/**
* Associated file location.
*/
location
?:
Location
;
}
//#endregion
}
src/vs/workbench/api/browser/extensionHost.contribution.ts
浏览文件 @
d1280418
...
...
@@ -64,6 +64,7 @@ import './mainThreadLabelService';
import
'
./mainThreadTunnelService
'
;
import
'
./mainThreadAuthentication
'
;
import
'
./mainThreadTimeline
'
;
import
'
./mainThreadTesting
'
;
import
'
vs/workbench/api/common/apiCommands
'
;
export
class
ExtensionPoints
implements
IWorkbenchContribution
{
...
...
src/vs/workbench/api/browser/mainThreadTesting.ts
0 → 100644
浏览文件 @
d1280418
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
Disposable
,
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
getTestSubscriptionKey
,
RunTestsRequest
,
RunTestsResult
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
import
{
ITestService
}
from
'
vs/workbench/contrib/testing/common/testService
'
;
import
{
extHostNamedCustomer
}
from
'
vs/workbench/api/common/extHostCustomers
'
;
import
{
ExtHostContext
,
ExtHostTestingResource
,
ExtHostTestingShape
,
IExtHostContext
,
MainContext
,
MainThreadTestingShape
}
from
'
../common/extHost.protocol
'
;
import
{
URI
,
UriComponents
}
from
'
vs/base/common/uri
'
;
@
extHostNamedCustomer
(
MainContext
.
MainThreadTesting
)
export
class
MainThreadTesting
extends
Disposable
implements
MainThreadTestingShape
{
private
readonly
proxy
:
ExtHostTestingShape
;
private
readonly
testSubscriptions
=
new
Map
<
string
,
IDisposable
>
();
constructor
(
extHostContext
:
IExtHostContext
,
@
ITestService
private
readonly
testService
:
ITestService
,
)
{
super
();
this
.
proxy
=
extHostContext
.
getProxy
(
ExtHostContext
.
ExtHostTesting
);
this
.
_register
(
this
.
testService
.
onShouldSubscribe
(
args
=>
this
.
proxy
.
$subscribeToTests
(
args
.
resource
,
args
.
uri
)));
this
.
_register
(
this
.
testService
.
onShouldUnsubscribe
(
args
=>
this
.
proxy
.
$unsubscribeFromTests
(
args
.
resource
,
args
.
uri
)));
}
/**
* @inheritdoc
*/
public
$registerTestProvider
(
id
:
string
)
{
this
.
testService
.
registerTestController
(
id
,
{
runTests
:
req
=>
this
.
proxy
.
$runTestsForProvider
(
req
),
});
}
/**
* @inheritdoc
*/
public
$unregisterTestProvider
(
id
:
string
)
{
this
.
testService
.
unregisterTestController
(
id
);
}
/**
* @inheritdoc
*/
$subscribeToDiffs
(
resource
:
ExtHostTestingResource
,
uriComponents
:
UriComponents
):
void
{
const
uri
=
URI
.
revive
(
uriComponents
);
const
disposable
=
this
.
testService
.
subscribeToDiffs
(
resource
,
uri
,
diff
=>
this
.
proxy
.
$acceptDiff
(
resource
,
uriComponents
,
diff
));
this
.
testSubscriptions
.
set
(
getTestSubscriptionKey
(
resource
,
uri
),
disposable
);
}
/**
* @inheritdoc
*/
public
$unsubscribeFromDiffs
(
resource
:
ExtHostTestingResource
,
uriComponents
:
UriComponents
):
void
{
const
key
=
getTestSubscriptionKey
(
resource
,
URI
.
revive
(
uriComponents
));
this
.
testSubscriptions
.
get
(
key
)?.
dispose
();
this
.
testSubscriptions
.
delete
(
key
);
}
/**
* @inheritdoc
*/
public
$publishDiff
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
,
diff
:
TestsDiff
):
void
{
this
.
testService
.
publishDiff
(
resource
,
URI
.
revive
(
uri
),
diff
);
}
public
$runTests
(
req
:
RunTestsRequest
):
Promise
<
RunTestsResult
>
{
return
this
.
testService
.
runTests
(
req
);
}
public
dispose
()
{
// no-op
}
}
src/vs/workbench/api/common/extHost.api.impl.ts
浏览文件 @
d1280418
...
...
@@ -81,6 +81,7 @@ import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEdito
import
{
ExtHostWebviewPanels
}
from
'
vs/workbench/api/common/extHostWebviewPanels
'
;
import
{
ExtHostBulkEdits
}
from
'
vs/workbench/api/common/extHostBulkEdits
'
;
import
{
IExtHostFileSystemInfo
}
from
'
vs/workbench/api/common/extHostFileSystemInfo
'
;
import
{
ExtHostTesting
}
from
'
vs/workbench/api/common/extHostTesting
'
;
export
interface
IExtensionApiFactory
{
(
extension
:
IExtensionDescription
,
registry
:
ExtensionDescriptionRegistry
,
configProvider
:
ExtHostConfigProvider
):
typeof
vscode
;
...
...
@@ -152,6 +153,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const
extHostWebviewPanels
=
rpcProtocol
.
set
(
ExtHostContext
.
ExtHostWebviewPanels
,
new
ExtHostWebviewPanels
(
rpcProtocol
,
extHostWebviews
,
extHostWorkspace
));
const
extHostCustomEditors
=
rpcProtocol
.
set
(
ExtHostContext
.
ExtHostCustomEditors
,
new
ExtHostCustomEditors
(
rpcProtocol
,
extHostDocuments
,
extensionStoragePaths
,
extHostWebviews
,
extHostWebviewPanels
));
const
extHostWebviewViews
=
rpcProtocol
.
set
(
ExtHostContext
.
ExtHostWebviewViews
,
new
ExtHostWebviewViews
(
rpcProtocol
,
extHostWebviews
));
const
extHostTesting
=
rpcProtocol
.
set
(
ExtHostContext
.
ExtHostTesting
,
new
ExtHostTesting
(
rpcProtocol
,
extHostDocumentsAndEditors
,
extHostWorkspace
));
// Check that no named customers are missing
const
expected
:
ProxyIdentifier
<
any
>
[]
=
values
(
ExtHostContext
);
...
...
@@ -333,6 +335,25 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
?
extHostTypes
.
ExtensionKind
.
Workspace
:
extHostTypes
.
ExtensionKind
.
UI
;
const
test
:
typeof
vscode
.
test
=
{
registerTestProvider
(
provider
)
{
checkProposedApiEnabled
(
extension
);
return
extHostTesting
.
registerTestProvider
(
provider
);
},
createDocumentTestObserver
(
document
)
{
checkProposedApiEnabled
(
extension
);
return
extHostTesting
.
createTextDocumentTestObserver
(
document
);
},
createWorkspaceTestObserver
(
workspaceFolder
)
{
checkProposedApiEnabled
(
extension
);
return
extHostTesting
.
createWorkspaceTestObserver
(
workspaceFolder
);
},
runTests
(
provider
)
{
checkProposedApiEnabled
(
extension
);
return
extHostTesting
.
runTests
(
provider
);
},
};
// namespace: extensions
const
extensions
:
typeof
vscode
.
extensions
=
{
getExtension
(
extensionId
:
string
):
Extension
<
any
>
|
undefined
{
...
...
@@ -1072,6 +1093,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
extensions
,
languages
,
scm
,
test
,
comment
,
comments
,
tasks
,
...
...
@@ -1197,7 +1219,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
NotebookEditorRevealType
:
extHostTypes
.
NotebookEditorRevealType
,
NotebookCellOutput
:
extHostTypes
.
NotebookCellOutput
,
NotebookCellOutputItem
:
extHostTypes
.
NotebookCellOutputItem
,
OnTypeRenameRanges
:
extHostTypes
.
OnTypeRenameRanges
OnTypeRenameRanges
:
extHostTypes
.
OnTypeRenameRanges
,
TestRunState
:
extHostTypes
.
TestRunState
,
TestMessageSeverity
:
extHostTypes
.
TestMessageSeverity
,
TestState
:
extHostTypes
.
TestState
,
};
};
}
...
...
src/vs/workbench/api/common/extHost.protocol.ts
浏览文件 @
d1280418
...
...
@@ -57,6 +57,7 @@ import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib
import
{
DebugConfigurationProviderTriggerKind
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
IAccessibilityInformation
}
from
'
vs/platform/accessibility/common/accessibility
'
;
import
{
IExtensionIdWithVersion
}
from
'
vs/platform/userDataSync/common/extensionsStorageSync
'
;
import
{
RunTestForProviderRequest
,
RunTestsRequest
,
RunTestsResult
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
export
interface
IEnvironment
{
isExtensionDevelopmentDebug
:
boolean
;
...
...
@@ -1750,6 +1751,28 @@ export interface ExtHostTimelineShape {
$getTimeline
(
source
:
string
,
uri
:
UriComponents
,
options
:
TimelineOptions
,
token
:
CancellationToken
,
internalOptions
?:
InternalTimelineOptions
):
Promise
<
Timeline
|
undefined
>
;
}
export
const
enum
ExtHostTestingResource
{
Workspace
,
TextDocument
}
export
interface
ExtHostTestingShape
{
$runTestsForProvider
(
req
:
RunTestForProviderRequest
):
Promise
<
RunTestsResult
>
;
$subscribeToTests
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
):
void
;
$unsubscribeFromTests
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
):
void
;
$acceptDiff
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
,
diff
:
TestsDiff
):
void
;
}
export
interface
MainThreadTestingShape
{
$registerTestProvider
(
id
:
string
):
void
;
$unregisterTestProvider
(
id
:
string
):
void
;
$subscribeToDiffs
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
):
void
;
$unsubscribeFromDiffs
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
):
void
;
$publishDiff
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
,
diff
:
TestsDiff
):
void
;
$runTests
(
req
:
RunTestsRequest
):
Promise
<
RunTestsResult
>
;
}
// --- proxy identifiers
export
const
MainContext
=
{
...
...
@@ -1799,7 +1822,8 @@ export const MainContext = {
MainThreadNotebook
:
createMainId
<
MainThreadNotebookShape
>
(
'
MainThreadNotebook
'
),
MainThreadTheming
:
createMainId
<
MainThreadThemingShape
>
(
'
MainThreadTheming
'
),
MainThreadTunnelService
:
createMainId
<
MainThreadTunnelServiceShape
>
(
'
MainThreadTunnelService
'
),
MainThreadTimeline
:
createMainId
<
MainThreadTimelineShape
>
(
'
MainThreadTimeline
'
)
MainThreadTimeline
:
createMainId
<
MainThreadTimelineShape
>
(
'
MainThreadTimeline
'
),
MainThreadTesting
:
createMainId
<
MainThreadTestingShape
>
(
'
MainThreadTesting
'
),
};
export
const
ExtHostContext
=
{
...
...
@@ -1842,5 +1866,6 @@ export const ExtHostContext = {
ExtHostTheming
:
createMainId
<
ExtHostThemingShape
>
(
'
ExtHostTheming
'
),
ExtHostTunnelService
:
createMainId
<
ExtHostTunnelServiceShape
>
(
'
ExtHostTunnelService
'
),
ExtHostAuthentication
:
createMainId
<
ExtHostAuthenticationShape
>
(
'
ExtHostAuthentication
'
),
ExtHostTimeline
:
createMainId
<
ExtHostTimelineShape
>
(
'
ExtHostTimeline
'
)
ExtHostTimeline
:
createMainId
<
ExtHostTimelineShape
>
(
'
ExtHostTimeline
'
),
ExtHostTesting
:
createMainId
<
ExtHostTestingShape
>
(
'
ExtHostTesting
'
),
};
src/vs/workbench/api/common/extHostTesting.ts
0 → 100644
浏览文件 @
d1280418
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
mapFind
}
from
'
vs/base/common/arrays
'
;
import
{
disposableTimeout
}
from
'
vs/base/common/async
'
;
import
{
CancellationToken
}
from
'
vs/base/common/cancellation
'
;
import
{
throttle
}
from
'
vs/base/common/decorators
'
;
import
{
Emitter
}
from
'
vs/base/common/event
'
;
import
{
once
}
from
'
vs/base/common/functional
'
;
import
{
DisposableStore
,
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
isDefined
}
from
'
vs/base/common/types
'
;
import
{
URI
,
UriComponents
}
from
'
vs/base/common/uri
'
;
import
{
generateUuid
}
from
'
vs/base/common/uuid
'
;
import
{
ExtHostTestingResource
,
ExtHostTestingShape
,
MainContext
,
MainThreadTestingShape
}
from
'
vs/workbench/api/common/extHost.protocol
'
;
import
{
IExtHostDocumentsAndEditors
}
from
'
vs/workbench/api/common/extHostDocumentsAndEditors
'
;
import
{
IExtHostRpcService
}
from
'
vs/workbench/api/common/extHostRpcService
'
;
import
{
TestItem
}
from
'
vs/workbench/api/common/extHostTypeConverters
'
;
import
{
Disposable
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
IExtHostWorkspace
}
from
'
vs/workbench/api/common/extHostWorkspace
'
;
import
{
AbstractIncrementalTestCollection
,
EMPTY_TEST_RESULT
,
IncrementalTestCollectionItem
,
InternalTestItem
,
RunTestForProviderRequest
,
RunTestsResult
,
TestDiffOpType
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
import
type
*
as
vscode
from
'
vscode
'
;
const
getTestSubscriptionKey
=
(
resource
:
ExtHostTestingResource
,
uri
:
URI
)
=>
`
${
resource
}
:
${
uri
.
toString
()}
`
;
export
class
ExtHostTesting
implements
ExtHostTestingShape
{
private
readonly
providers
=
new
Map
<
string
,
vscode
.
TestProvider
>
();
private
readonly
proxy
:
MainThreadTestingShape
;
private
readonly
ownedTests
=
new
OwnedTestCollection
();
private
readonly
testSubscriptions
=
new
Map
<
string
,
{
collection
:
SingleUseTestCollection
,
store
:
IDisposable
}
>
();
private
workspaceObservers
:
WorkspaceFolderTestObserverFactory
;
private
textDocumentObservers
:
TextDocumentTestObserverFactory
;
constructor
(@
IExtHostRpcService
rpc
:
IExtHostRpcService
,
@
IExtHostDocumentsAndEditors
private
readonly
documents
:
IExtHostDocumentsAndEditors
,
@
IExtHostWorkspace
private
readonly
workspace
:
IExtHostWorkspace
)
{
this
.
proxy
=
rpc
.
getProxy
(
MainContext
.
MainThreadTesting
);
this
.
workspaceObservers
=
new
WorkspaceFolderTestObserverFactory
(
this
.
proxy
);
this
.
textDocumentObservers
=
new
TextDocumentTestObserverFactory
(
this
.
proxy
,
documents
);
}
/**
* Implements vscode.test.registerTestProvider
*/
public
registerTestProvider
<
T
extends
vscode
.
TestItem
>
(
provider
:
vscode
.
TestProvider
<
T
>
):
vscode
.
Disposable
{
const
providerId
=
generateUuid
();
this
.
providers
.
set
(
providerId
,
provider
);
this
.
proxy
.
$registerTestProvider
(
providerId
);
return
new
Disposable
(()
=>
{
this
.
providers
.
delete
(
providerId
);
this
.
proxy
.
$unregisterTestProvider
(
providerId
);
});
}
/**
* Implements vscode.test.createTextDocumentTestObserver
*/
public
createTextDocumentTestObserver
(
document
:
vscode
.
TextDocument
)
{
return
this
.
textDocumentObservers
.
checkout
(
document
.
uri
);
}
/**
* Implements vscode.test.createWorkspaceTestObserver
*/
public
createWorkspaceTestObserver
(
workspaceFolder
:
vscode
.
WorkspaceFolder
)
{
return
this
.
workspaceObservers
.
checkout
(
workspaceFolder
.
uri
);
}
/**
* Implements vscode.test.runTests
*/
public
async
runTests
(
req
:
vscode
.
TestRunOptions
<
vscode
.
TestItem
>
)
{
await
this
.
proxy
.
$runTests
({
tests
:
req
.
tests
// Find workspace items first, then owned tests, then document tests.
// If a test instance exists in both the workspace and document, prefer
// the workspace because it's less ephemeral.
.
map
(
test
=>
this
.
workspaceObservers
.
getMirroredTestDataByReference
(
test
)
??
mapFind
(
this
.
testSubscriptions
.
values
(),
c
=>
c
.
collection
.
getTestByReference
(
test
))
??
this
.
textDocumentObservers
.
getMirroredTestDataByReference
(
test
))
.
filter
(
isDefined
)
.
map
(
item
=>
({
providerId
:
item
.
providerId
,
testId
:
item
.
id
})),
debug
:
req
.
debug
});
}
/**
* Handles a request to read tests for a file, or workspace.
* @override
*/
public
$subscribeToTests
(
resource
:
ExtHostTestingResource
,
uriComponents
:
UriComponents
)
{
const
uri
=
URI
.
revive
(
uriComponents
);
const
subscriptionKey
=
getTestSubscriptionKey
(
resource
,
uri
);
if
(
this
.
testSubscriptions
.
has
(
subscriptionKey
))
{
return
;
}
let
method
:
undefined
|
((
p
:
vscode
.
TestProvider
)
=>
vscode
.
TestHierarchy
<
vscode
.
TestItem
>
|
undefined
);
if
(
resource
===
ExtHostTestingResource
.
TextDocument
)
{
const
document
=
this
.
documents
.
getDocument
(
uri
);
if
(
document
)
{
method
=
p
=>
p
.
createDocumentTestHierarchy
?.(
document
.
document
);
}
}
else
{
const
folder
=
this
.
workspace
.
getWorkspaceFolder
(
uri
,
false
);
if
(
folder
)
{
method
=
p
=>
p
.
createWorkspaceTestHierarchy
?.(
folder
);
}
}
if
(
!
method
)
{
return
;
}
const
disposable
=
new
DisposableStore
();
const
collection
=
disposable
.
add
(
this
.
ownedTests
.
createForHierarchy
(
diff
=>
this
.
proxy
.
$publishDiff
(
resource
,
uriComponents
,
diff
)));
for
(
const
[
id
,
provider
]
of
this
.
providers
)
{
try
{
const
hierarchy
=
method
(
provider
);
if
(
!
hierarchy
)
{
continue
;
}
disposable
.
add
(
hierarchy
);
collection
.
addRoot
(
hierarchy
.
root
,
id
);
hierarchy
.
onDidChangeTest
(
e
=>
collection
.
onItemChange
(
e
,
id
));
}
catch
(
e
)
{
console
.
error
(
e
);
}
}
this
.
testSubscriptions
.
set
(
subscriptionKey
,
{
store
:
disposable
,
collection
});
}
/**
* Disposes of a previous subscription to tests.
* @override
*/
public
$unsubscribeFromTests
(
resource
:
ExtHostTestingResource
,
uriComponents
:
UriComponents
)
{
const
uri
=
URI
.
revive
(
uriComponents
);
const
subscriptionKey
=
getTestSubscriptionKey
(
resource
,
uri
);
this
.
testSubscriptions
.
get
(
subscriptionKey
)?.
store
.
dispose
();
this
.
testSubscriptions
.
delete
(
subscriptionKey
);
}
/**
* Receives a test update from the main thread. Called (eventually) whenever
* tests change.
* @override
*/
public
$acceptDiff
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
,
diff
:
TestsDiff
):
void
{
if
(
resource
===
ExtHostTestingResource
.
TextDocument
)
{
this
.
textDocumentObservers
.
acceptDiff
(
URI
.
revive
(
uri
),
diff
);
}
else
{
this
.
workspaceObservers
.
acceptDiff
(
URI
.
revive
(
uri
),
diff
);
}
}
/**
* Runs tests with the given set of IDs. Allows for test from multiple
* providers to be run.
* @override
*/
public
async
$runTestsForProvider
(
req
:
RunTestForProviderRequest
):
Promise
<
RunTestsResult
>
{
const
provider
=
this
.
providers
.
get
(
req
.
providerId
);
if
(
!
provider
||
!
provider
.
runTests
)
{
return
EMPTY_TEST_RESULT
;
}
const
tests
=
req
.
ids
.
map
(
id
=>
this
.
ownedTests
.
getTestById
(
id
)?.
actual
).
filter
(
isDefined
);
if
(
!
tests
.
length
)
{
return
EMPTY_TEST_RESULT
;
}
await
provider
.
runTests
({
tests
,
debug
:
req
.
debug
},
CancellationToken
.
None
);
return
EMPTY_TEST_RESULT
;
}
}
const
keyMap
:
{
[
K
in
keyof
Omit
<
Required
<
vscode
.
TestItem
>
,
'
children
'
>
]:
null
}
=
{
label
:
null
,
location
:
null
,
state
:
null
,
debuggable
:
null
,
description
:
null
,
runnable
:
null
};
const
simpleProps
=
Object
.
keys
(
keyMap
)
as
ReadonlyArray
<
keyof
typeof
keyMap
>
;
const
itemEqualityComparator
=
(
a
:
vscode
.
TestItem
)
=>
{
const
values
:
unknown
[]
=
[];
for
(
const
prop
of
simpleProps
)
{
values
.
push
(
a
[
prop
]);
}
return
(
b
:
vscode
.
TestItem
)
=>
{
for
(
let
i
=
0
;
i
<
simpleProps
.
length
;
i
++
)
{
if
(
values
[
i
]
!==
b
[
simpleProps
[
i
]])
{
return
false
;
}
}
return
true
;
};
};
/**
* @private
*/
export
interface
OwnedCollectionTestItem
extends
InternalTestItem
{
actual
:
vscode
.
TestItem
;
previousChildren
:
Set
<
string
>
;
previousEquals
:
(
v
:
vscode
.
TestItem
)
=>
boolean
;
}
export
class
OwnedTestCollection
{
protected
readonly
testIdToInternal
=
new
Map
<
string
,
OwnedCollectionTestItem
>
();
/**
* Gets test information by ID, if it was defined and still exists in this
* extension host.
*/
public
getTestById
(
id
:
string
)
{
return
this
.
testIdToInternal
.
get
(
id
);
}
/**
* Creates a new test collection for a specific hierarchy for a workspace
* or document observation.
*/
public
createForHierarchy
(
publishDiff
:
(
diff
:
TestsDiff
)
=>
void
=
()
=>
undefined
)
{
return
new
SingleUseTestCollection
(
this
.
testIdToInternal
,
publishDiff
);
}
}
/**
* Maintains tests created and registered for a single set of hierarchies
* for a workspace or document.
* @private
*/
export
class
SingleUseTestCollection
implements
IDisposable
{
protected
readonly
testItemToInternal
=
new
Map
<
vscode
.
TestItem
,
OwnedCollectionTestItem
>
();
protected
diff
:
TestsDiff
=
[];
private
disposed
=
false
;
constructor
(
private
readonly
testIdToInternal
:
Map
<
string
,
OwnedCollectionTestItem
>
,
private
readonly
publishDiff
:
(
diff
:
TestsDiff
)
=>
void
)
{
}
/**
* Adds a new root node to the collection.
*/
public
addRoot
(
item
:
vscode
.
TestItem
,
providerId
:
string
)
{
this
.
addItem
(
item
,
providerId
,
null
);
this
.
throttleSendDiff
();
}
/**
* Gets test information by its reference, if it was defined and still exists
* in this extension host.
*/
public
getTestByReference
(
item
:
vscode
.
TestItem
)
{
return
this
.
testItemToInternal
.
get
(
item
);
}
/**
* Should be called when an item change is fired on the test provider.
*/
public
onItemChange
(
item
:
vscode
.
TestItem
,
providerId
:
string
)
{
const
existing
=
this
.
testItemToInternal
.
get
(
item
);
if
(
!
existing
)
{
if
(
!
this
.
disposed
)
{
console
.
warn
(
`Received a TestProvider.onDidChangeTest for a test that wasn't seen before as a child.`
);
}
return
;
}
this
.
addItem
(
item
,
providerId
,
existing
.
parent
);
this
.
throttleSendDiff
();
}
/**
* Gets a diff of all changes that have been made, and clears the diff queue.
*/
public
collectDiff
()
{
const
diff
=
this
.
diff
;
this
.
diff
=
[];
return
diff
;
}
public
dispose
()
{
for
(
const
item
of
this
.
testItemToInternal
.
values
())
{
this
.
testIdToInternal
.
delete
(
item
.
id
);
}
this
.
testIdToInternal
.
clear
();
this
.
diff
=
[];
this
.
disposed
=
true
;
}
protected
getId
():
string
{
return
generateUuid
();
}
private
addItem
(
actual
:
vscode
.
TestItem
,
providerId
:
string
,
parent
:
string
|
null
)
{
let
internal
=
this
.
testItemToInternal
.
get
(
actual
);
if
(
!
internal
)
{
internal
=
{
actual
,
id
:
this
.
getId
(),
parent
,
item
:
TestItem
.
from
(
actual
),
providerId
,
previousChildren
:
new
Set
(),
previousEquals
:
itemEqualityComparator
(
actual
),
};
this
.
testItemToInternal
.
set
(
actual
,
internal
);
this
.
testIdToInternal
.
set
(
internal
.
id
,
internal
);
this
.
diff
.
push
([
TestDiffOpType
.
Add
,
{
id
:
internal
.
id
,
parent
,
providerId
,
item
:
internal
.
item
}]);
}
else
if
(
!
internal
.
previousEquals
(
actual
))
{
internal
.
item
=
TestItem
.
from
(
actual
);
internal
.
previousEquals
=
itemEqualityComparator
(
actual
);
this
.
diff
.
push
([
TestDiffOpType
.
Update
,
{
id
:
internal
.
id
,
parent
,
providerId
,
item
:
internal
.
item
}]);
}
// If there are children, track which ones are deleted
// and recursively and/update them.
if
(
actual
.
children
)
{
const
deletedChildren
=
internal
.
previousChildren
;
const
currentChildren
=
new
Set
<
string
>
();
for
(
const
child
of
actual
.
children
)
{
const
c
=
this
.
addItem
(
child
,
providerId
,
internal
.
id
);
deletedChildren
.
delete
(
c
.
id
);
currentChildren
.
add
(
c
.
id
);
}
for
(
const
child
of
deletedChildren
)
{
this
.
removeItembyId
(
child
);
}
internal
.
previousChildren
=
currentChildren
;
}
return
internal
;
}
private
removeItembyId
(
id
:
string
)
{
this
.
diff
.
push
([
TestDiffOpType
.
Remove
,
id
]);
const
queue
=
[
this
.
testIdToInternal
.
get
(
id
)];
while
(
queue
.
length
)
{
const
item
=
queue
.
pop
();
if
(
!
item
)
{
continue
;
}
this
.
testIdToInternal
.
delete
(
item
.
id
);
this
.
testItemToInternal
.
delete
(
item
.
actual
);
for
(
const
child
of
item
.
previousChildren
)
{
queue
.
push
(
this
.
testIdToInternal
.
get
(
child
));
}
}
}
@
throttle
(
200
)
protected
throttleSendDiff
()
{
const
diff
=
this
.
collectDiff
();
if
(
diff
.
length
)
{
this
.
publishDiff
(
diff
);
}
}
}
/**
* @private
*/
interface
MirroredCollectionTestItem
extends
IncrementalTestCollectionItem
{
revived
:
vscode
.
TestItem
;
wrapped
?:
vscode
.
TestItem
;
}
/**
* Maintains tests in this extension host sent from the main thread.
* @private
*/
export
class
MirroredTestCollection
extends
AbstractIncrementalTestCollection
<
MirroredCollectionTestItem
>
{
private
changeEmitter
=
new
Emitter
<
vscode
.
TestItem
|
null
>
();
/**
* Change emitter that fires with the same sematics as `TestObserver.onDidChangeTests`.
*/
public
readonly
onDidChangeTests
=
this
.
changeEmitter
.
event
;
/**
* Mapping of mirrored test items to their underlying ID. Given here to avoid
* exposing them to extensions.
*/
protected
readonly
mirroredTestIds
=
new
WeakMap
<
vscode
.
TestItem
,
string
>
();
/**
* Gets a list of root test items.
*/
public
get
rootTestItems
()
{
return
this
.
getAllAsTestItem
([...
this
.
roots
]);
}
/**
* Translates the item IDs to TestItems for exposure to extensions.
*/
public
getAllAsTestItem
(
itemIds
:
ReadonlyArray
<
string
>
):
vscode
.
TestItem
[]
{
return
itemIds
.
map
(
itemId
=>
{
const
item
=
this
.
items
.
get
(
itemId
);
return
item
&&
this
.
createCollectionItemWrapper
(
item
);
}).
filter
(
isDefined
);
}
/**
* If the test item is a mirrored test item, returns its underlying ID.
*/
public
getMirroredTestDataByReference
(
item
:
vscode
.
TestItem
)
{
const
itemId
=
this
.
mirroredTestIds
.
get
(
item
);
return
itemId
?
this
.
items
.
get
(
itemId
)
:
undefined
;
}
/**
* @override
*/
protected
createItem
(
item
:
InternalTestItem
):
MirroredCollectionTestItem
{
return
{
...
item
,
revived
:
TestItem
.
to
(
item
.
item
),
children
:
new
Set
()
};
}
/**
* @override
*/
protected
onChange
(
item
:
MirroredCollectionTestItem
|
null
)
{
if
(
item
)
{
Object
.
assign
(
item
.
revived
,
TestItem
.
to
(
item
.
item
));
}
this
.
changeEmitter
.
fire
(
item
?
this
.
createCollectionItemWrapper
(
item
)
:
null
);
}
private
createCollectionItemWrapper
(
item
:
MirroredCollectionTestItem
):
vscode
.
TestItem
{
if
(
!
item
.
wrapped
)
{
item
.
wrapped
=
createMirroredTestItem
(
item
,
this
);
this
.
mirroredTestIds
.
set
(
item
.
wrapped
,
item
.
id
);
}
return
item
.
wrapped
;
}
}
const
createMirroredTestItem
=
(
internal
:
MirroredCollectionTestItem
,
collection
:
MirroredTestCollection
):
vscode
.
TestItem
=>
{
const
obj
=
{};
Object
.
defineProperty
(
obj
,
'
children
'
,
{
enumerable
:
true
,
configurable
:
false
,
get
:
()
=>
collection
.
getAllAsTestItem
([...
internal
.
children
])
});
simpleProps
.
forEach
(
prop
=>
Object
.
defineProperty
(
obj
,
prop
,
{
enumerable
:
true
,
configurable
:
false
,
get
:
()
=>
internal
.
revived
[
prop
],
}));
return
obj
as
any
;
};
interface
IObserverData
{
observers
:
number
;
tests
:
MirroredTestCollection
;
listener
:
IDisposable
;
pendingDeletion
?:
IDisposable
;
}
abstract
class
AbstractTestObserverFactory
{
private
readonly
resources
=
new
Map
<
string
/* uri */
,
IObserverData
>
();
public
checkout
(
resourceUri
:
URI
):
vscode
.
TestObserver
{
const
resourceKey
=
resourceUri
.
toString
();
const
resource
=
this
.
resources
.
get
(
resourceKey
)
??
this
.
createObserverData
(
resourceUri
);
resource
.
observers
++
;
return
{
onDidChangeTest
:
resource
.
tests
.
onDidChangeTests
,
onDidDiscoverInitialTests
:
new
Emitter
<
void
>
().
event
,
// todo@connor4312
get
tests
()
{
return
resource
.
tests
.
rootTestItems
;
},
dispose
:
once
(()
=>
{
if
(
!--
resource
.
observers
)
{
resource
.
pendingDeletion
=
this
.
eventuallyDispose
(
resourceUri
);
}
}),
};
}
/**
* Gets the internal test data by its reference, in any observer.
*/
public
getMirroredTestDataByReference
(
ref
:
vscode
.
TestItem
)
{
for
(
const
{
tests
}
of
this
.
resources
.
values
())
{
const
v
=
tests
.
getMirroredTestDataByReference
(
ref
);
if
(
v
)
{
return
v
;
}
}
return
undefined
;
}
/**
* Called when no observers are listening for the resource any more. Should
* defer unlistening on the resource, and return a disposiable
* to halt the process in case new listeners come in.
*/
protected
eventuallyDispose
(
resourceUri
:
URI
)
{
return
disposableTimeout
(()
=>
this
.
unlisten
(
resourceUri
),
10
*
1000
);
}
/**
* Starts listening to test information for the given resource.
*/
protected
abstract
listen
(
resourceUri
:
URI
,
onDiff
:
(
diff
:
TestsDiff
)
=>
void
):
Disposable
;
private
createObserverData
(
resourceUri
:
URI
):
IObserverData
{
const
tests
=
new
MirroredTestCollection
();
const
listener
=
this
.
listen
(
resourceUri
,
diff
=>
tests
.
apply
(
diff
));
const
data
:
IObserverData
=
{
observers
:
0
,
tests
,
listener
};
this
.
resources
.
set
(
resourceUri
.
toString
(),
data
);
return
data
;
}
/**
* Called when a resource is no longer in use.
*/
protected
unlisten
(
resourceUri
:
URI
)
{
const
key
=
resourceUri
.
toString
();
const
resource
=
this
.
resources
.
get
(
key
);
if
(
resource
)
{
resource
.
observers
=
-
1
;
resource
.
pendingDeletion
?.
dispose
();
resource
.
listener
.
dispose
();
this
.
resources
.
delete
(
key
);
}
}
}
class
WorkspaceFolderTestObserverFactory
extends
AbstractTestObserverFactory
{
private
diffListeners
=
new
Map
<
string
,
(
diff
:
TestsDiff
)
=>
void
>
();
constructor
(
private
readonly
proxy
:
MainThreadTestingShape
)
{
super
();
}
/**
* Publishees the diff for the workspace folder with the given uri.
*/
public
acceptDiff
(
resourceUri
:
URI
,
diff
:
TestsDiff
)
{
this
.
diffListeners
.
get
(
resourceUri
.
toString
())?.(
diff
);
}
/**
* @override
*/
public
listen
(
resourceUri
:
URI
,
onDiff
:
(
diff
:
TestsDiff
)
=>
void
)
{
this
.
proxy
.
$subscribeToDiffs
(
ExtHostTestingResource
.
Workspace
,
resourceUri
);
const
uriString
=
resourceUri
.
toString
();
this
.
diffListeners
.
set
(
uriString
,
onDiff
);
return
new
Disposable
(()
=>
{
this
.
proxy
.
$unsubscribeFromDiffs
(
ExtHostTestingResource
.
Workspace
,
resourceUri
);
this
.
diffListeners
.
delete
(
uriString
);
});
}
}
class
TextDocumentTestObserverFactory
extends
AbstractTestObserverFactory
{
private
diffListeners
=
new
Map
<
string
,
(
diff
:
TestsDiff
)
=>
void
>
();
constructor
(
private
readonly
proxy
:
MainThreadTestingShape
,
private
documents
:
IExtHostDocumentsAndEditors
)
{
super
();
}
/**
* Publishees the diff for the document with the given uri.
*/
public
acceptDiff
(
resourceUri
:
URI
,
diff
:
TestsDiff
)
{
this
.
diffListeners
.
get
(
resourceUri
.
toString
())?.(
diff
);
}
/**
* @override
*/
public
listen
(
resourceUri
:
URI
,
onDiff
:
(
diff
:
TestsDiff
)
=>
void
)
{
const
document
=
this
.
documents
.
getDocument
(
resourceUri
);
if
(
!
document
)
{
return
new
Disposable
(()
=>
undefined
);
}
const
uriString
=
resourceUri
.
toString
();
this
.
diffListeners
.
set
(
uriString
,
onDiff
);
const
disposeListener
=
this
.
documents
.
onDidRemoveDocuments
(
evt
=>
{
if
(
evt
.
some
(
delta
=>
delta
.
document
.
uri
.
toString
()
===
uriString
))
{
this
.
unlisten
(
resourceUri
);
}
});
this
.
proxy
.
$subscribeToDiffs
(
ExtHostTestingResource
.
TextDocument
,
resourceUri
);
return
new
Disposable
(()
=>
{
this
.
proxy
.
$unsubscribeFromDiffs
(
ExtHostTestingResource
.
TextDocument
,
resourceUri
);
disposeListener
.
dispose
();
this
.
diffListeners
.
delete
(
uriString
);
});
}
}
src/vs/workbench/api/common/extHostTypeConverters.ts
浏览文件 @
d1280418
...
...
@@ -32,6 +32,7 @@ import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
import
{
CommandsConverter
}
from
'
vs/workbench/api/common/extHostCommands
'
;
import
{
ExtHostNotebookController
}
from
'
vs/workbench/api/common/extHostNotebook
'
;
import
{
CellOutputKind
,
IDisplayOutput
,
INotebookDecorationRenderOptions
}
from
'
vs/workbench/contrib/notebook/common/notebookCommon
'
;
import
{
ITestItem
,
ITestState
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
export
interface
PositionLike
{
line
:
number
;
...
...
@@ -1396,3 +1397,64 @@ export namespace NotebookDecorationRenderOptions {
};
}
}
export
namespace
TestState
{
export
function
from
(
item
:
vscode
.
TestState
):
ITestState
{
return
{
runState
:
item
.
runState
,
duration
:
item
.
duration
,
messages
:
item
.
messages
.
map
(
message
=>
({
message
:
MarkdownString
.
fromStrict
(
message
.
message
)
||
''
,
severity
:
message
.
severity
,
expectedOutput
:
message
.
expectedOutput
,
actualOutput
:
message
.
actualOutput
,
location
:
message
.
location
?
location
.
from
(
message
.
location
)
:
undefined
,
})),
};
}
export
function
to
(
item
:
ITestState
):
vscode
.
TestState
{
return
new
types
.
TestState
(
item
.
runState
,
item
.
messages
.
map
(
message
=>
({
message
:
typeof
message
.
message
===
'
string
'
?
message
.
message
:
MarkdownString
.
to
(
message
.
message
),
severity
:
message
.
severity
,
expectedOutput
:
message
.
expectedOutput
,
actualOutput
:
message
.
actualOutput
,
location
:
message
.
location
&&
location
.
to
({
range
:
message
.
location
.
range
,
uri
:
URI
.
revive
(
message
.
location
.
uri
)
}),
})),
item
.
duration
,
);
}
}
export
namespace
TestItem
{
export
function
from
(
item
:
vscode
.
TestItem
):
ITestItem
{
return
{
label
:
item
.
label
,
location
:
item
.
location
?
location
.
from
(
item
.
location
)
:
undefined
,
debuggable
:
item
.
debuggable
,
description
:
item
.
description
,
runnable
:
item
.
runnable
,
state
:
TestState
.
from
(
item
.
state
),
};
}
export
function
to
(
item
:
ITestItem
):
vscode
.
TestItem
{
return
{
label
:
item
.
label
,
location
:
item
.
location
&&
location
.
to
({
range
:
item
.
location
.
range
,
uri
:
URI
.
revive
(
item
.
location
.
uri
)
}),
debuggable
:
item
.
debuggable
,
description
:
item
.
description
,
runnable
:
item
.
runnable
,
state
:
TestState
.
to
(
item
.
state
),
};
}
}
src/vs/workbench/api/common/extHostTypes.ts
浏览文件 @
d1280418
...
...
@@ -2897,3 +2897,47 @@ export class OnTypeRenameRanges {
constructor
(
public
readonly
ranges
:
Range
[],
public
readonly
wordPattern
?:
RegExp
)
{
}
}
//#region Testing
export
enum
TestRunState
{
Unset
=
0
,
Running
=
1
,
Passed
=
2
,
Failed
=
3
,
Skipped
=
4
,
Errored
=
5
}
export
enum
TestMessageSeverity
{
Error
=
0
,
Warning
=
1
,
Information
=
2
,
Hint
=
3
}
@
es5ClassCompat
export
class
TestState
{
#
runState
:
TestRunState
;
#
duration
?:
number
;
#
messages
:
ReadonlyArray
<
Readonly
<
vscode
.
TestMessage
>>
;
public
get
runState
()
{
return
this
.
#
runState
;
}
public
get
duration
()
{
return
this
.
#
duration
;
}
public
get
messages
()
{
return
this
.
#
messages
;
}
constructor
(
runState
:
TestRunState
,
messages
:
vscode
.
TestMessage
[]
=
[],
duration
?:
number
)
{
this
.
#
runState
=
runState
;
this
.
#
messages
=
Object
.
freeze
(
messages
.
map
(
m
=>
Object
.
freeze
(
m
)));
this
.
#
duration
=
duration
;
}
}
//#endregion
src/vs/workbench/contrib/testing/browser/testing.contribution.ts
0 → 100644
浏览文件 @
d1280418
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
registerSingleton
}
from
'
vs/platform/instantiation/common/extensions
'
;
import
{
ITestService
}
from
'
vs/workbench/contrib/testing/common/testService
'
;
import
{
TestService
}
from
'
vs/workbench/contrib/testing/common/testServiceImpl
'
;
registerSingleton
(
ITestService
,
TestService
);
src/vs/workbench/contrib/testing/common/testCollection.ts
0 → 100644
浏览文件 @
d1280418
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
IMarkdownString
}
from
'
vs/base/common/htmlContent
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
Location
as
ModeLocation
}
from
'
vs/editor/common/modes
'
;
import
{
ExtHostTestingResource
}
from
'
vs/workbench/api/common/extHost.protocol
'
;
import
{
TestMessageSeverity
,
TestRunState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
/**
* Request to them main thread to run a set of tests.
*/
export
interface
RunTestsRequest
{
tests
:
{
testId
:
string
;
providerId
:
string
}[];
debug
:
boolean
;
}
/**
* Request from the main thread to run tests for a single provider.
*/
export
interface
RunTestForProviderRequest
{
providerId
:
string
;
ids
:
string
[];
debug
:
boolean
;
}
/**
* Response to a {@link RunTestsRequest}
*/
export
interface
RunTestsResult
{
// todo
}
export
const
EMPTY_TEST_RESULT
:
RunTestsResult
=
{};
export
const
collectTestResults
=
(
results
:
ReadonlyArray
<
RunTestsResult
>
)
=>
{
return
results
[
0
]
||
{};
// todo
};
export
interface
ITestMessage
{
message
:
string
|
IMarkdownString
;
severity
:
TestMessageSeverity
|
undefined
;
expectedOutput
:
string
|
undefined
;
actualOutput
:
string
|
undefined
;
location
:
ModeLocation
|
undefined
;
}
export
interface
ITestState
{
runState
:
TestRunState
;
duration
:
number
|
undefined
;
messages
:
ITestMessage
[];
}
/**
* The TestItem from .d.ts, as a plain object without children.
*/
export
interface
ITestItem
{
label
:
string
;
children
?:
never
;
location
:
ModeLocation
|
undefined
;
description
:
string
|
undefined
;
runnable
:
boolean
|
undefined
;
debuggable
:
boolean
|
undefined
;
state
:
ITestState
;
}
/**
* TestItem-like shape, butm with an ID and children as strings.
*/
export
interface
InternalTestItem
{
id
:
string
;
providerId
:
string
;
parent
:
string
|
null
;
item
:
ITestItem
;
}
export
const
enum
TestDiffOpType
{
Add
,
Update
,
Remove
,
}
export
type
TestsDiffOp
=
|
[
op
:
TestDiffOpType
.
Add
,
item
:
InternalTestItem
]
|
[
op
:
TestDiffOpType
.
Update
,
item
:
InternalTestItem
]
|
[
op
:
TestDiffOpType
.
Remove
,
itemId
:
string
];
/**
* Utility function to get a unique string for a subscription to a resource,
* useful to keep maps of document or workspace folder subscription info.
*/
export
const
getTestSubscriptionKey
=
(
resource
:
ExtHostTestingResource
,
uri
:
URI
)
=>
`
${
resource
}
:
${
uri
.
toString
()}
`
;
/**
* Request from the ext host or main thread to indicate that tests have
* changed. It's assumed that any item upserted *must* have its children
* previously also upserted, or upserted as part of the same operation.
* Children that no longer exist in an upserted item will be removed.
*/
export
type
TestsDiff
=
TestsDiffOp
[];
/**
* @private
*/
export
interface
IncrementalTestCollectionItem
extends
InternalTestItem
{
children
:
Set
<
string
>
;
}
/**
* Maintains tests in this extension host sent from the main thread.
*/
export
abstract
class
AbstractIncrementalTestCollection
<
T
extends
IncrementalTestCollectionItem
>
{
/**
* Map of item IDs to test item objects.
*/
protected
readonly
items
=
new
Map
<
string
,
T
>
();
/**
* ID of test root items.
*/
protected
readonly
roots
=
new
Set
<
string
>
();
/**
* Applies the diff to the collection.
*/
public
apply
(
diff
:
TestsDiff
)
{
for
(
const
op
of
diff
)
{
switch
(
op
[
0
])
{
case
TestDiffOpType
.
Add
:
{
const
item
=
op
[
1
];
if
(
!
item
.
parent
)
{
this
.
roots
.
add
(
item
.
id
);
this
.
items
.
set
(
item
.
id
,
this
.
createItem
(
item
));
this
.
onChange
(
null
);
}
else
if
(
this
.
items
.
has
(
item
.
parent
))
{
const
parent
=
this
.
items
.
get
(
item
.
parent
)
!
;
parent
.
children
.
add
(
item
.
id
);
this
.
items
.
set
(
item
.
id
,
this
.
createItem
(
item
));
this
.
onChange
(
parent
);
}
break
;
}
case
TestDiffOpType
.
Update
:
{
const
item
=
op
[
1
];
const
existing
=
this
.
items
.
get
(
item
.
id
);
if
(
existing
)
{
Object
.
assign
(
existing
.
item
,
item
.
item
);
this
.
onChange
(
existing
);
}
break
;
}
case
TestDiffOpType
.
Remove
:
{
const
toRemove
=
this
.
items
.
get
(
op
[
1
]);
if
(
!
toRemove
)
{
break
;
}
if
(
toRemove
.
parent
)
{
this
.
items
.
get
(
toRemove
.
parent
)
!
.
children
.
delete
(
toRemove
.
id
);
}
else
{
this
.
roots
.
delete
(
toRemove
.
id
);
}
const
queue
:
Iterable
<
string
>
[]
=
[[
op
[
1
]]];
while
(
queue
.
length
)
{
for
(
const
itemId
of
queue
.
pop
()
!
)
{
const
existing
=
this
.
items
.
get
(
itemId
);
if
(
existing
)
{
queue
.
push
(
existing
.
children
);
this
.
items
.
delete
(
itemId
);
}
}
}
this
.
onChange
(
toRemove
);
}
}
}
}
/**
* Called when an item in the collection changes, with the same semantics
* as `onDidChangeTests` in vscode.d.ts.
*/
protected
onChange
(
item
:
T
|
null
):
void
{
// no-op
}
/**
* Creates a new item for the collection from the internal test item.
*/
protected
abstract
createItem
(
internal
:
InternalTestItem
):
T
;
}
src/vs/workbench/contrib/testing/common/testService.ts
0 → 100644
浏览文件 @
d1280418
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
Event
}
from
'
vs/base/common/event
'
;
import
{
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
URI
}
from
'
vs/base/common/uri
'
;
import
{
createDecorator
}
from
'
vs/platform/instantiation/common/instantiation
'
;
import
{
ExtHostTestingResource
}
from
'
vs/workbench/api/common/extHost.protocol
'
;
import
{
RunTestForProviderRequest
,
RunTestsRequest
,
RunTestsResult
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
export
const
ITestService
=
createDecorator
<
ITestService
>
(
'
testService
'
);
export
interface
MainTestController
{
runTests
(
request
:
RunTestForProviderRequest
):
Promise
<
RunTestsResult
>
;
}
export
type
TestDiffListener
=
(
diff
:
TestsDiff
)
=>
void
;
export
interface
ITestService
{
readonly
_serviceBrand
:
undefined
;
readonly
onShouldSubscribe
:
Event
<
{
resource
:
ExtHostTestingResource
,
uri
:
URI
}
>
;
readonly
onShouldUnsubscribe
:
Event
<
{
resource
:
ExtHostTestingResource
,
uri
:
URI
}
>
;
registerTestController
(
id
:
string
,
controller
:
MainTestController
):
void
;
unregisterTestController
(
id
:
string
):
void
;
runTests
(
req
:
RunTestsRequest
):
Promise
<
RunTestsResult
>
;
publishDiff
(
resource
:
ExtHostTestingResource
,
uri
:
URI
,
diff
:
TestsDiff
):
void
;
subscribeToDiffs
(
resource
:
ExtHostTestingResource
,
uri
:
URI
,
acceptDiff
:
TestDiffListener
):
IDisposable
;
}
src/vs/workbench/contrib/testing/common/testServiceImpl.ts
0 → 100644
浏览文件 @
d1280418
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
{
groupBy
}
from
'
vs/base/common/arrays
'
;
import
{
Emitter
}
from
'
vs/base/common/event
'
;
import
{
Disposable
,
toDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
isDefined
}
from
'
vs/base/common/types
'
;
import
{
URI
,
UriComponents
}
from
'
vs/base/common/uri
'
;
import
{
ExtHostTestingResource
}
from
'
vs/workbench/api/common/extHost.protocol
'
;
import
{
AbstractIncrementalTestCollection
,
collectTestResults
,
getTestSubscriptionKey
,
IncrementalTestCollectionItem
,
InternalTestItem
,
RunTestsRequest
,
RunTestsResult
,
TestDiffOpType
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
import
{
ITestService
,
MainTestController
,
TestDiffListener
}
from
'
vs/workbench/contrib/testing/common/testService
'
;
export
class
TestService
extends
Disposable
implements
ITestService
{
declare
readonly
_serviceBrand
:
undefined
;
private
testControllers
=
new
Map
<
string
,
MainTestController
>
();
private
readonly
testSubscriptions
=
new
Map
<
string
,
{
collection
:
MainThreadTestCollection
;
onDiff
:
Emitter
<
TestsDiff
>
;
listeners
:
number
;
}
>
();
private
readonly
subscribeEmitter
=
new
Emitter
<
{
resource
:
ExtHostTestingResource
,
uri
:
URI
}
>
();
private
readonly
unsubscribeEmitter
=
new
Emitter
<
{
resource
:
ExtHostTestingResource
,
uri
:
URI
}
>
();
/**
* Fired when extension hosts should pull events from their test factories.
*/
public
readonly
onShouldSubscribe
=
this
.
subscribeEmitter
.
event
;
/**
* Fired when extension hosts should stop pulling events from their test factories.
*/
public
readonly
onShouldUnsubscribe
=
this
.
unsubscribeEmitter
.
event
;
/**
* @inheritdoc
*/
async
runTests
(
req
:
RunTestsRequest
):
Promise
<
RunTestsResult
>
{
const
tests
=
groupBy
(
req
.
tests
,
(
a
,
b
)
=>
a
.
providerId
===
b
.
providerId
?
0
:
1
);
const
requests
=
tests
.
map
(
group
=>
{
const
providerId
=
group
[
0
].
providerId
;
const
controller
=
this
.
testControllers
.
get
(
providerId
);
return
controller
?.
runTests
({
providerId
,
debug
:
req
.
debug
,
ids
:
group
.
map
(
t
=>
t
.
testId
)
});
}).
filter
(
isDefined
);
return
collectTestResults
(
await
Promise
.
all
(
requests
));
}
/**
* @inheritdoc
*/
public
subscribeToDiffs
(
resource
:
ExtHostTestingResource
,
uri
:
URI
,
acceptDiff
:
TestDiffListener
)
{
const
subscriptionKey
=
getTestSubscriptionKey
(
resource
,
uri
);
let
subscription
=
this
.
testSubscriptions
.
get
(
subscriptionKey
);
if
(
!
subscription
)
{
subscription
=
{
collection
:
new
MainThreadTestCollection
(),
listeners
:
0
,
onDiff
:
new
Emitter
()
};
this
.
subscribeEmitter
.
fire
({
resource
,
uri
});
this
.
testSubscriptions
.
set
(
subscriptionKey
,
subscription
);
}
subscription
.
listeners
++
;
const
revive
=
subscription
.
collection
.
getReviverDiff
();
if
(
revive
.
length
)
{
acceptDiff
(
revive
);
}
const
listener
=
subscription
.
onDiff
.
event
(
acceptDiff
);
return
toDisposable
(()
=>
{
listener
.
dispose
();
if
(
!--
subscription
!
.
listeners
)
{
this
.
unsubscribeEmitter
.
fire
({
resource
,
uri
});
this
.
testSubscriptions
.
delete
(
subscriptionKey
);
}
});
}
/**
* @inheritdoc
*/
public
publishDiff
(
resource
:
ExtHostTestingResource
,
uri
:
UriComponents
,
diff
:
TestsDiff
)
{
const
sub
=
this
.
testSubscriptions
.
get
(
getTestSubscriptionKey
(
resource
,
URI
.
revive
(
uri
)));
if
(
sub
)
{
sub
.
collection
.
apply
(
diff
);
sub
.
onDiff
.
fire
(
diff
);
}
}
/**
* @inheritdoc
*/
public
registerTestController
(
id
:
string
,
controller
:
MainTestController
):
void
{
this
.
testControllers
.
set
(
id
,
controller
);
}
/**
* @inheritdoc
*/
public
unregisterTestController
(
id
:
string
):
void
{
this
.
testControllers
.
delete
(
id
);
}
}
class
MainThreadTestCollection
extends
AbstractIncrementalTestCollection
<
IncrementalTestCollectionItem
>
{
/**
* Gets a diff that adds all items currently in the tree to a new collection,
* allowing it to fully hydrate.
*/
public
getReviverDiff
()
{
const
ops
:
TestsDiff
=
[];
const
queue
=
[
this
.
roots
];
while
(
queue
.
length
)
{
for
(
const
child
of
queue
.
pop
()
!
)
{
const
item
=
this
.
items
.
get
(
child
)
!
;
ops
.
push
([
TestDiffOpType
.
Add
,
{
id
:
item
.
id
,
providerId
:
item
.
providerId
,
item
:
item
.
item
,
parent
:
item
.
parent
}]);
queue
.
push
(
item
.
children
);
}
}
return
ops
;
}
protected
createItem
(
internal
:
InternalTestItem
):
IncrementalTestCollectionItem
{
return
{
...
internal
,
children
:
new
Set
()
};
}
}
src/vs/workbench/test/browser/api/extHostTesting.test.ts
0 → 100644
浏览文件 @
d1280418
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import
*
as
assert
from
'
assert
'
;
import
{
MirroredTestCollection
,
OwnedTestCollection
,
SingleUseTestCollection
}
from
'
vs/workbench/api/common/extHostTesting
'
;
import
*
as
convert
from
'
vs/workbench/api/common/extHostTypeConverters
'
;
import
{
TestRunState
,
TestState
}
from
'
vs/workbench/api/common/extHostTypes
'
;
import
{
TestDiffOpType
,
TestsDiff
}
from
'
vs/workbench/contrib/testing/common/testCollection
'
;
import
{
TestItem
}
from
'
vscode
'
;
suite
(
'
ExtHost Testing
'
,
()
=>
{
let
single
:
TestSingleUseCollection
;
let
owned
:
TestOwnedTestCollection
;
setup
(()
=>
{
owned
=
new
TestOwnedTestCollection
();
single
=
owned
.
createForHierarchy
(
d
=>
single
.
setDiff
(
d
/* don't clear during testing */
));
});
teardown
(()
=>
{
single
.
dispose
();
assert
.
deepEqual
(
owned
.
idToInternal
.
size
,
0
,
'
expected owned ids to be empty after dispose
'
);
});
suite
(
'
OwnedTestCollection
'
,
()
=>
{
test
(
'
adds a root recursively
'
,
()
=>
{
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
assert
.
deepStrictEqual
(
single
.
collectDiff
(),
[
[
TestDiffOpType
.
Add
,
{
id
:
'
0
'
,
providerId
:
'
pid
'
,
parent
:
null
,
item
:
convert
.
TestItem
.
from
(
stubTest
(
'
root
'
))
}],
[
TestDiffOpType
.
Add
,
{
id
:
'
1
'
,
providerId
:
'
pid
'
,
parent
:
'
0
'
,
item
:
convert
.
TestItem
.
from
(
stubTest
(
'
a
'
))
}],
[
TestDiffOpType
.
Add
,
{
id
:
'
2
'
,
providerId
:
'
pid
'
,
parent
:
'
1
'
,
item
:
convert
.
TestItem
.
from
(
stubTest
(
'
aa
'
))
}],
[
TestDiffOpType
.
Add
,
{
id
:
'
3
'
,
providerId
:
'
pid
'
,
parent
:
'
1
'
,
item
:
convert
.
TestItem
.
from
(
stubTest
(
'
ab
'
))
}],
[
TestDiffOpType
.
Add
,
{
id
:
'
4
'
,
providerId
:
'
pid
'
,
parent
:
'
0
'
,
item
:
convert
.
TestItem
.
from
(
stubTest
(
'
b
'
))
}],
]);
});
test
(
'
no-ops if items not changed
'
,
()
=>
{
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
single
.
collectDiff
();
assert
.
deepStrictEqual
(
single
.
collectDiff
(),
[]);
});
test
(
'
watches property mutations
'
,
()
=>
{
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
single
.
collectDiff
();
tests
.
children
!
[
0
].
description
=
'
Hello world
'
;
/* item a */
single
.
onItemChange
(
tests
,
'
pid
'
);
assert
.
deepStrictEqual
(
single
.
collectDiff
(),
[
[
TestDiffOpType
.
Update
,
{
id
:
'
1
'
,
parent
:
'
0
'
,
providerId
:
'
pid
'
,
item
:
convert
.
TestItem
.
from
({
...
stubTest
(
'
a
'
),
description
:
'
Hello world
'
})
}],
]);
single
.
onItemChange
(
tests
,
'
pid
'
);
assert
.
deepStrictEqual
(
single
.
collectDiff
(),
[]);
});
test
(
'
removes children
'
,
()
=>
{
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
single
.
collectDiff
();
tests
.
children
!
.
splice
(
0
,
1
);
single
.
onItemChange
(
tests
,
'
pid
'
);
assert
.
deepStrictEqual
(
single
.
collectDiff
(),
[
[
TestDiffOpType
.
Remove
,
'
1
'
],
]);
assert
.
deepStrictEqual
([...
owned
.
idToInternal
.
keys
()].
sort
(),
[
'
0
'
,
'
4
'
]);
assert
.
strictEqual
(
single
.
itemToInternal
.
size
,
2
);
});
test
(
'
adds new children
'
,
()
=>
{
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
single
.
collectDiff
();
const
child
=
stubTest
(
'
ac
'
);
tests
.
children
!
[
0
].
children
!
.
push
(
child
);
single
.
onItemChange
(
tests
,
'
pid
'
);
assert
.
deepStrictEqual
(
single
.
collectDiff
(),
[
[
TestDiffOpType
.
Add
,
{
id
:
'
5
'
,
providerId
:
'
pid
'
,
parent
:
'
1
'
,
item
:
convert
.
TestItem
.
from
(
child
)
}],
]);
assert
.
deepStrictEqual
([...
owned
.
idToInternal
.
keys
()].
sort
(),
[
'
0
'
,
'
1
'
,
'
2
'
,
'
3
'
,
'
4
'
,
'
5
'
]);
assert
.
strictEqual
(
single
.
itemToInternal
.
size
,
6
);
});
});
suite
(
'
MirroredTestCollection
'
,
()
=>
{
test
(
'
mirrors creation of the root
'
,
()
=>
{
const
m
=
new
TestMirroredCollection
();
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
m
.
apply
(
single
.
collectDiff
());
assertTreesEqual
(
m
.
rootTestItems
[
0
],
owned
.
getTestById
(
'
0
'
)
!
.
actual
);
assert
.
strictEqual
(
m
.
length
,
single
.
itemToInternal
.
size
);
});
test
(
'
mirrors node deletion
'
,
()
=>
{
const
m
=
new
TestMirroredCollection
();
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
m
.
apply
(
single
.
collectDiff
());
tests
.
children
!
.
splice
(
0
,
1
);
single
.
onItemChange
(
tests
,
'
pid
'
);
m
.
apply
(
single
.
collectDiff
());
assertTreesEqual
(
m
.
rootTestItems
[
0
],
owned
.
getTestById
(
'
0
'
)
!
.
actual
);
assert
.
strictEqual
(
m
.
length
,
single
.
itemToInternal
.
size
);
});
test
(
'
mirrors node addition
'
,
()
=>
{
const
m
=
new
TestMirroredCollection
();
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
m
.
apply
(
single
.
collectDiff
());
tests
.
children
!
[
0
].
children
!
.
push
(
stubTest
(
'
ac
'
));
single
.
onItemChange
(
tests
,
'
pid
'
);
m
.
apply
(
single
.
collectDiff
());
assertTreesEqual
(
m
.
rootTestItems
[
0
],
owned
.
getTestById
(
'
0
'
)
!
.
actual
);
assert
.
strictEqual
(
m
.
length
,
single
.
itemToInternal
.
size
);
});
test
(
'
mirrors node update
'
,
()
=>
{
const
m
=
new
TestMirroredCollection
();
const
tests
=
stubNestedTests
();
single
.
addRoot
(
tests
,
'
pid
'
);
m
.
apply
(
single
.
collectDiff
());
tests
.
children
!
[
0
].
description
=
'
Hello world
'
;
/* item a */
single
.
onItemChange
(
tests
,
'
pid
'
);
m
.
apply
(
single
.
collectDiff
());
assertTreesEqual
(
m
.
rootTestItems
[
0
],
owned
.
getTestById
(
'
0
'
)
!
.
actual
);
});
});
});
const
stubTest
=
(
label
:
string
):
TestItem
=>
({
label
,
location
:
undefined
,
state
:
new
TestState
(
TestRunState
.
Unset
),
debuggable
:
true
,
runnable
:
true
,
description
:
''
});
const
assertTreesEqual
=
(
a
:
TestItem
,
b
:
TestItem
)
=>
{
assert
.
deepStrictEqual
({
...
a
,
children
:
undefined
},
{
...
b
,
children
:
undefined
});
const
aChildren
=
(
a
.
children
??
[]).
sort
();
const
bChildren
=
(
b
.
children
??
[]).
sort
();
assert
.
strictEqual
(
aChildren
.
length
,
bChildren
.
length
,
`expected
${
a
.
label
}
.children.length ==
${
b
.
label
}
.children.length`
);
aChildren
.
forEach
((
_
,
i
)
=>
assertTreesEqual
(
aChildren
[
i
],
bChildren
[
i
]));
};
const
stubNestedTests
=
()
=>
({
...
stubTest
(
'
root
'
),
children
:
[
{
...
stubTest
(
'
a
'
),
children
:
[
stubTest
(
'
aa
'
),
stubTest
(
'
ab
'
)]
},
stubTest
(
'
b
'
),
]
});
class
TestOwnedTestCollection
extends
OwnedTestCollection
{
public
get
idToInternal
()
{
return
this
.
testIdToInternal
;
}
public
createForHierarchy
(
publishDiff
:
(
diff
:
TestsDiff
)
=>
void
=
()
=>
undefined
)
{
return
new
TestSingleUseCollection
(
this
.
testIdToInternal
,
publishDiff
);
}
}
class
TestSingleUseCollection
extends
SingleUseTestCollection
{
private
idCounter
=
0
;
public
get
itemToInternal
()
{
return
this
.
testItemToInternal
;
}
public
get
currentDiff
()
{
return
this
.
diff
;
}
protected
getId
()
{
return
String
(
this
.
idCounter
++
);
}
public
setDiff
(
diff
:
TestsDiff
)
{
this
.
diff
=
diff
;
}
}
class
TestMirroredCollection
extends
MirroredTestCollection
{
public
get
length
()
{
return
this
.
items
.
size
;
}
}
src/vs/workbench/workbench.common.main.ts
浏览文件 @
d1280418
...
...
@@ -156,6 +156,9 @@ import 'vs/workbench/contrib/performance/browser/performance.contribution';
// Notebook
import
'
vs/workbench/contrib/notebook/browser/notebook.contribution
'
;
// Testing
import
'
vs/workbench/contrib/testing/browser/testing.contribution
'
;
// Logs
import
'
vs/workbench/contrib/logs/common/logs.contribution
'
;
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录