2
2
* Copyright 2021 The Go Authors. All rights reserved.
3
3
* Licensed under the MIT License. See LICENSE in the project root for license information.
4
4
*--------------------------------------------------------*/
5
- import { Memento , TestItem , Uri } from 'vscode' ;
5
+ import {
6
+ EventEmitter ,
7
+ Memento ,
8
+ Range ,
9
+ TestItem ,
10
+ TextDocumentShowOptions ,
11
+ TreeDataProvider ,
12
+ TreeItem ,
13
+ TreeItemCollapsibleState ,
14
+ Uri
15
+ } from 'vscode' ;
6
16
import vscode = require( 'vscode' ) ;
7
17
import { getTempFilePath } from '../util' ;
8
18
import { GoTestResolver } from './resolve' ;
@@ -13,7 +23,10 @@ const optionsMemento = 'testProfilingOptions';
13
23
const defaultOptions : ProfilingOptions = { kind : 'cpu' } ;
14
24
15
25
export class GoTestProfiler {
16
- private readonly lastRunFor = new Map < string , Run > ( ) ;
26
+ public readonly view = new ProfileTreeDataProvider ( this ) ;
27
+
28
+ // Maps test IDs to profile files. See docs/test-explorer.md for details.
29
+ private readonly runs = new Map < string , File [ ] > ( ) ;
17
30
18
31
constructor ( private readonly resolver : GoTestResolver , private readonly workspaceState : Memento ) { }
19
32
@@ -24,27 +37,28 @@ export class GoTestProfiler {
24
37
this . workspaceState . update ( optionsMemento , v ) ;
25
38
}
26
39
27
- preRun ( options : ProfilingOptions , items : TestItem [ ] ) : string [ ] {
40
+ preRun ( options : ProfilingOptions , item : TestItem ) : string [ ] {
28
41
const kind = Kind . get ( options . kind ) ;
29
- if ( ! kind ) {
30
- items . forEach ( ( x ) => this . lastRunFor . delete ( x . id ) ) ;
31
- return [ ] ;
32
- }
42
+ if ( ! kind ) return [ ] ;
33
43
34
44
const flags = [ ] ;
35
- const run = new Run ( items , kind ) ;
36
- flags . push ( run . file . flag ) ;
37
- items . forEach ( ( x ) => this . lastRunFor . set ( x . id , run ) ) ;
45
+ const run = new File ( kind , item ) ;
46
+ flags . push ( run . flag ) ;
47
+ if ( this . runs . has ( item . id ) ) this . runs . get ( item . id ) . unshift ( run ) ;
48
+ else this . runs . set ( item . id , [ run ] ) ;
38
49
return flags ;
39
50
}
40
51
41
52
postRun ( ) {
42
53
// Update the list of tests that have profiles.
43
- vscode . commands . executeCommand ( 'setContext' , 'go.profiledTests' , Array . from ( this . lastRunFor . keys ( ) ) ) ;
54
+ vscode . commands . executeCommand ( 'setContext' , 'go.profiledTests' , Array . from ( this . runs . keys ( ) ) ) ;
55
+ vscode . commands . executeCommand ( 'setContext' , 'go.hasProfiles' , this . runs . size > 0 ) ;
56
+
57
+ this . view . didRun ( ) ;
44
58
}
45
59
46
60
hasProfileFor ( id : string ) : boolean {
47
- return this . lastRunFor . has ( id ) ;
61
+ return this . runs . has ( id ) ;
48
62
}
49
63
50
64
async configure ( ) : Promise < ProfilingOptions | undefined > {
@@ -68,13 +82,31 @@ export class GoTestProfiler {
68
82
return ;
69
83
}
70
84
71
- const run = this . lastRunFor . get ( item . id ) ;
72
- if ( ! run ) {
73
- await vscode . window . showErrorMessage ( `${ name } was not profiled the last time it was run ` ) ;
85
+ const runs = this . runs . get ( item . id ) ;
86
+ if ( ! runs || runs . length === 0 ) {
87
+ await vscode . window . showErrorMessage ( `${ name } has not been profiled ` ) ;
74
88
return ;
75
89
}
76
90
77
- await run . file . show ( ) ;
91
+ await runs [ 0 ] . show ( ) ;
92
+ }
93
+
94
+ // Tests that have been profiled
95
+ get tests ( ) {
96
+ const items = Array . from ( this . runs . keys ( ) ) ;
97
+ items . sort ( ( a : string , b : string ) => {
98
+ const aWhen = this . runs . get ( a ) [ 0 ] . when . getTime ( ) ;
99
+ const bWhen = this . runs . get ( b ) [ 0 ] . when . getTime ( ) ;
100
+ return bWhen - aWhen ;
101
+ } ) ;
102
+
103
+ // Filter out any tests that no longer exist
104
+ return items . map ( ( x ) => this . resolver . all . get ( x ) ) . filter ( ( x ) => x ) ;
105
+ }
106
+
107
+ // Profiles associated with the given test
108
+ get ( item : TestItem ) {
109
+ return this . runs . get ( item . id ) || [ ] ;
78
110
}
79
111
}
80
112
@@ -103,23 +135,20 @@ class Kind {
103
135
static readonly Block = new Kind ( 'block' , 'Block' , '--blockprofile' ) ;
104
136
}
105
137
106
- class Run {
138
+ class File {
107
139
private static nextID = 0 ;
108
140
141
+ public readonly id = File . nextID ++ ;
109
142
public readonly when = new Date ( ) ;
110
- public readonly id = Run . nextID ++ ;
111
- public readonly file : File ;
112
143
113
- constructor ( public readonly targets : TestItem [ ] , kind : Kind ) {
114
- this . file = new File ( this , kind ) ;
115
- }
116
- }
144
+ constructor ( public readonly kind : Kind , public readonly target : TestItem ) { }
117
145
118
- class File {
119
- constructor ( public readonly run : Run , public readonly kind : Kind ) { }
146
+ get label ( ) {
147
+ return `${ this . kind . label } @ ${ this . when . toTimeString ( ) } ` ;
148
+ }
120
149
121
150
get name ( ) {
122
- return `profile-${ this . run . id } .${ this . kind . id } .prof` ;
151
+ return `profile-${ this . id } .${ this . kind . id } .prof` ;
123
152
}
124
153
125
154
get flag ( ) : string {
@@ -134,3 +163,48 @@ class File {
134
163
await vscode . window . showTextDocument ( this . uri ) ;
135
164
}
136
165
}
166
+
167
+ type TreeElement = TestItem | File ;
168
+
169
+ class ProfileTreeDataProvider implements TreeDataProvider < TreeElement > {
170
+ private readonly didChangeTreeData = new EventEmitter < void | TreeElement > ( ) ;
171
+ public readonly onDidChangeTreeData = this . didChangeTreeData . event ;
172
+
173
+ constructor ( private readonly profiler : GoTestProfiler ) { }
174
+
175
+ didRun ( ) {
176
+ this . didChangeTreeData . fire ( ) ;
177
+ }
178
+
179
+ getTreeItem ( element : TreeElement ) : TreeItem {
180
+ if ( element instanceof File ) {
181
+ const item = new TreeItem ( element . label ) ;
182
+ item . contextValue = 'file' ;
183
+ item . command = {
184
+ title : 'Open' ,
185
+ command : 'vscode.open' ,
186
+ arguments : [ element . uri ]
187
+ } ;
188
+ return item ;
189
+ }
190
+
191
+ const item = new TreeItem ( element . label , TreeItemCollapsibleState . Collapsed ) ;
192
+ item . contextValue = 'test' ;
193
+ const options : TextDocumentShowOptions = {
194
+ preserveFocus : false ,
195
+ selection : new Range ( element . range . start , element . range . start )
196
+ } ;
197
+ item . command = {
198
+ title : 'Go to test' ,
199
+ command : 'vscode.open' ,
200
+ arguments : [ element . uri , options ]
201
+ } ;
202
+ return item ;
203
+ }
204
+
205
+ getChildren ( element ?: TreeElement ) : TreeElement [ ] {
206
+ if ( ! element ) return this . profiler . tests ;
207
+ if ( element instanceof File ) return [ ] ;
208
+ return this . profiler . get ( element ) ;
209
+ }
210
+ }
0 commit comments