Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
849784e2
Unverified
Commit
849784e2
authored
Dec 23, 2020
by
Casey Hillers
Committed by
GitHub
Dec 23, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[devicelab] Add results path flag to test runner (#72765)
parent
dafc13f1
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
218 additions
and
29 deletions
+218
-29
run.dart
dev/devicelab/bin/run.dart
+30
-15
test_runner.dart
dev/devicelab/bin/test_runner.dart
+21
-0
upload_metrics.dart
dev/devicelab/lib/command/upload_metrics.dart
+32
-0
cocoon.dart
dev/devicelab/lib/framework/cocoon.dart
+69
-7
cocoon_test.dart
dev/devicelab/test/cocoon_test.dart
+66
-7
No files found.
dev/devicelab/bin/run.dart
View file @
849784e2
...
...
@@ -42,6 +42,9 @@ String luciBuilder;
/// Whether to exit on first test failure.
bool
exitOnFirstTestFailure
;
/// Path to write test results to.
String
resultsPath
;
/// File containing a service account token.
///
/// If passed, the test run results will be uploaded to Flutter infrastructure.
...
...
@@ -65,6 +68,16 @@ Future<void> main(List<String> rawArgs) async {
return
;
}
deviceId
=
args
[
'device-id'
]
as
String
;
exitOnFirstTestFailure
=
args
[
'exit'
]
as
bool
;
gitBranch
=
args
[
'git-branch'
]
as
String
;
localEngine
=
args
[
'local-engine'
]
as
String
;
localEngineSrcPath
=
args
[
'local-engine-src-path'
]
as
String
;
luciBuilder
=
args
[
'luci-builder'
]
as
String
;
resultsPath
=
args
[
'results-file'
]
as
String
;
serviceAccountTokenFile
=
args
[
'service-account-token-file'
]
as
String
;
silent
=
args
[
'silent'
]
as
bool
;
if
(!
args
.
wasParsed
(
'task'
))
{
if
(
args
.
wasParsed
(
'stage'
)
||
args
.
wasParsed
(
'all'
))
{
addTasks
(
...
...
@@ -89,15 +102,6 @@ Future<void> main(List<String> rawArgs) async {
return
;
}
deviceId
=
args
[
'device-id'
]
as
String
;
exitOnFirstTestFailure
=
args
[
'exit'
]
as
bool
;
gitBranch
=
args
[
'git-branch'
]
as
String
;
localEngine
=
args
[
'local-engine'
]
as
String
;
localEngineSrcPath
=
args
[
'local-engine-src-path'
]
as
String
;
luciBuilder
=
args
[
'luci-builder'
]
as
String
;
serviceAccountTokenFile
=
args
[
'service-account-token-file'
]
as
String
;
silent
=
args
[
'silent'
]
as
bool
;
if
(
args
.
wasParsed
(
'ab'
))
{
await
_runABTest
();
}
else
{
...
...
@@ -120,8 +124,17 @@ Future<void> _runTasks() async {
print
(
const
JsonEncoder
.
withIndent
(
' '
).
convert
(
result
));
section
(
'Finished task "
$taskName
"'
);
if
(
serviceAccountTokenFile
!=
null
)
{
if
(
resultsPath
!=
null
)
{
final
Cocoon
cocoon
=
Cocoon
();
await
cocoon
.
writeTaskResultToFile
(
builderName:
luciBuilder
,
gitBranch:
gitBranch
,
result:
result
,
resultsPath:
resultsPath
,
);
}
else
if
(
serviceAccountTokenFile
!=
null
)
{
final
Cocoon
cocoon
=
Cocoon
(
serviceAccountTokenPath:
serviceAccountTokenFile
);
/// Cocoon references LUCI tasks by the [luciBuilder] instead of [taskName].
await
cocoon
.
sendTaskResult
(
builderName:
luciBuilder
,
result:
result
,
gitBranch:
gitBranch
);
}
...
...
@@ -224,7 +237,7 @@ File _uniqueFile(String filenameTemplate) {
File
file
=
File
(
parts
[
0
]
+
parts
[
1
]);
int
i
=
1
;
while
(
file
.
existsSync
())
{
file
=
File
(
parts
[
0
]
+
i
.
toString
()+
parts
[
1
]);
file
=
File
(
parts
[
0
]
+
i
.
toString
()
+
parts
[
1
]);
i
++;
}
return
file
;
...
...
@@ -355,10 +368,7 @@ final ArgParser _argParser = ArgParser()
'locally. Defaults to
\
$FLUTTER_ENGINE
if set, or tries to guess at
\n
'
'the location based on the value of the --flutter-root option.'
,
)
..
addOption
(
'luci-builder'
,
help:
'[Flutter infrastructure] Name of the LUCI builder being run on.'
)
..
addOption
(
'luci-builder'
,
help:
'[Flutter infrastructure] Name of the LUCI builder being run on.'
)
..
addFlag
(
'match-host-platform'
,
defaultsTo:
true
,
...
...
@@ -367,6 +377,11 @@ final ArgParser _argParser = ArgParser()
'on a windows host). Each test publishes its '
'`required_agent_capabilities`
\n
in the `manifest.yaml` file.'
,
)
..
addOption
(
'results-file'
,
help:
'[Flutter infrastructure] File path for test results. If passed with
\n
'
'task, will write test results to the file.'
)
..
addOption
(
'service-account-token-file'
,
help:
'[Flutter infrastructure] Authentication for uploading results.'
,
...
...
dev/devicelab/bin/test_runner.dart
0 → 100644
View file @
849784e2
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:io'
;
import
'package:args/command_runner.dart'
;
import
'package:flutter_devicelab/command/upload_metrics.dart'
;
final
CommandRunner
<
void
>
runner
=
CommandRunner
<
void
>(
'devicelab_runner'
,
'DeviceLab test runner for recording performance metrics on applications'
)
..
addCommand
(
UploadMetricsCommand
());
Future
<
void
>
main
(
List
<
String
>
rawArgs
)
async
{
runner
.
run
(
rawArgs
).
catchError
((
dynamic
error
)
{
stderr
.
writeln
(
'
$error
\n
'
);
stderr
.
writeln
(
'Usage:
\n
'
);
stderr
.
writeln
(
runner
.
usage
);
exit
(
64
);
// Exit code 64 indicates a usage error.
});
}
dev/devicelab/lib/command/upload_metrics.dart
0 → 100644
View file @
849784e2
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:args/command_runner.dart'
;
import
'../framework/cocoon.dart'
;
class
UploadMetricsCommand
extends
Command
<
void
>
{
UploadMetricsCommand
()
{
argParser
.
addOption
(
'results-file'
,
help:
'Test results JSON to upload to Cocoon.'
);
argParser
.
addOption
(
'service-account-token-file'
,
help:
'Authentication token for uploading results.'
,
);
}
@override
String
get
name
=>
'upload-metrics'
;
@override
String
get
description
=>
'[Flutter infrastructure] Upload metrics data to Cocoon'
;
@override
Future
<
void
>
run
()
async
{
final
String
resultsPath
=
argResults
[
'results-file'
]
as
String
;
final
String
serviceAccountTokenFile
=
argResults
[
'service-account-token-file'
]
as
String
;
final
Cocoon
cocoon
=
Cocoon
(
serviceAccountTokenPath:
serviceAccountTokenFile
);
return
cocoon
.
sendResultsPath
(
resultsPath
);
}
}
dev/devicelab/lib/framework/cocoon.dart
View file @
849784e2
...
...
@@ -34,9 +34,9 @@ class Cocoon {
Cocoon
({
String
serviceAccountTokenPath
,
@visibleForTesting
Client
httpClient
,
@visibleForTesting
FileSystem
filesystem
,
@visibleForTesting
this
.
fs
=
const
LocalFileSystem
()
,
@visibleForTesting
this
.
processRunSync
=
Process
.
runSync
,
})
:
_httpClient
=
AuthenticatedCocoonClient
(
serviceAccountTokenPath
,
httpClient:
httpClient
,
filesystem:
f
ilesystem
);
})
:
_httpClient
=
AuthenticatedCocoonClient
(
serviceAccountTokenPath
,
httpClient:
httpClient
,
filesystem:
f
s
);
/// Client to make http requests to Cocoon.
final
AuthenticatedCocoonClient
_httpClient
;
...
...
@@ -46,6 +46,9 @@ class Cocoon {
/// Url used to send results to.
static
const
String
baseCocoonApiUrl
=
'https://flutter-dashboard.appspot.com/api'
;
/// Underlying [FileSystem] to use.
final
FileSystem
fs
;
static
final
Logger
logger
=
Logger
(
'CocoonClient'
);
String
get
commitSha
=>
_commitSha
??
_readCommitSha
();
...
...
@@ -61,8 +64,25 @@ class Cocoon {
return
_commitSha
=
result
.
stdout
as
String
;
}
/// Upload the JSON results in [resultsPath] to Cocoon.
///
/// Flutter infrastructure's workflow is:
/// 1. Run DeviceLab test, writing results to a known path
/// 2. Request service account token from luci auth (valid for at least 3 minutes)
/// 3. Upload results from (1) to Cocooon
Future
<
void
>
sendResultsPath
(
String
resultsPath
)
async
{
final
File
resultFile
=
fs
.
file
(
resultsPath
);
final
Map
<
String
,
dynamic
>
resultsJson
=
json
.
decode
(
await
resultFile
.
readAsString
())
as
Map
<
String
,
dynamic
>;
await
_sendUpdateTaskRequest
(
resultsJson
);
}
/// Send [TaskResult] to Cocoon.
Future
<
void
>
sendTaskResult
({
@required
String
builderName
,
@required
TaskResult
result
,
@required
String
gitBranch
})
async
{
// TODO(chillers): Remove when sendResultsPath is used in prod. https://github.com/flutter/flutter/issues/72457
Future
<
void
>
sendTaskResult
({
@required
String
builderName
,
@required
TaskResult
result
,
@required
String
gitBranch
,
})
async
{
assert
(
builderName
!=
null
);
assert
(
gitBranch
!=
null
);
assert
(
result
!=
null
);
...
...
@@ -73,7 +93,45 @@ class Cocoon {
print
(
'
${rec.level.name}
:
${rec.time}
:
${rec.message}
'
);
});
final
Map
<
String
,
dynamic
>
status
=
<
String
,
dynamic
>{
final
Map
<
String
,
dynamic
>
updateRequest
=
_constructUpdateRequest
(
gitBranch:
gitBranch
,
builderName:
builderName
,
result:
result
,
);
await
_sendUpdateTaskRequest
(
updateRequest
);
}
/// Write the given parameters into an update task request and store the JSON in [resultsPath].
Future
<
void
>
writeTaskResultToFile
({
@required
String
builderName
,
@required
String
gitBranch
,
@required
TaskResult
result
,
@required
String
resultsPath
,
})
async
{
assert
(
builderName
!=
null
);
assert
(
gitBranch
!=
null
);
assert
(
result
!=
null
);
assert
(
resultsPath
!=
null
);
final
Map
<
String
,
dynamic
>
updateRequest
=
_constructUpdateRequest
(
gitBranch:
gitBranch
,
builderName:
builderName
,
result:
result
,
);
final
File
resultFile
=
fs
.
file
(
resultsPath
);
if
(
resultFile
.
existsSync
())
{
resultFile
.
deleteSync
();
}
resultFile
.
createSync
();
resultFile
.
writeAsStringSync
(
json
.
encode
(
updateRequest
));
}
Map
<
String
,
dynamic
>
_constructUpdateRequest
({
@required
String
builderName
,
@required
TaskResult
result
,
@required
String
gitBranch
,
})
{
final
Map
<
String
,
dynamic
>
updateRequest
=
<
String
,
dynamic
>{
'CommitBranch'
:
gitBranch
,
'CommitSha'
:
commitSha
,
'BuilderName'
:
builderName
,
...
...
@@ -81,7 +139,7 @@ class Cocoon {
};
// Make a copy of result data because we may alter it for validation below.
status
[
'ResultData'
]
=
result
.
data
;
updateRequest
[
'ResultData'
]
=
result
.
data
;
final
List
<
String
>
validScoreKeys
=
<
String
>[];
if
(
result
.
benchmarkScoreKeys
!=
null
)
{
...
...
@@ -95,9 +153,13 @@ class Cocoon {
}
}
}
status
[
'BenchmarkScoreKeys'
]
=
validScoreKeys
;
updateRequest
[
'BenchmarkScoreKeys'
]
=
validScoreKeys
;
return
updateRequest
;
}
final
Map
<
String
,
dynamic
>
response
=
await
_sendCocoonRequest
(
'update-task-status'
,
status
);
Future
<
void
>
_sendUpdateTaskRequest
(
Map
<
String
,
dynamic
>
postBody
)
async
{
final
Map
<
String
,
dynamic
>
response
=
await
_sendCocoonRequest
(
'update-task-status'
,
postBody
);
if
(
response
[
'Name'
]
!=
null
)
{
logger
.
info
(
'Updated Cocoon with results from this task'
);
}
else
{
...
...
dev/devicelab/test/cocoon_test.dart
View file @
849784e2
...
...
@@ -48,7 +48,7 @@ void main() {
_processResult
=
ProcessResult
(
1
,
0
,
commitSha
,
''
);
cocoon
=
Cocoon
(
serviceAccountTokenPath:
serviceAccountTokenPath
,
f
ilesystem
:
fs
,
f
s
:
fs
,
httpClient:
mockClient
,
processRunSync:
runSyncStub
,
);
...
...
@@ -60,7 +60,7 @@ void main() {
_processResult
=
ProcessResult
(
1
,
1
,
''
,
''
);
cocoon
=
Cocoon
(
serviceAccountTokenPath:
serviceAccountTokenPath
,
f
ilesystem
:
fs
,
f
s
:
fs
,
httpClient:
mockClient
,
processRunSync:
runSyncStub
,
);
...
...
@@ -68,12 +68,69 @@ void main() {
expect
(()
=>
cocoon
.
commitSha
,
throwsA
(
isA
<
CocoonException
>()));
});
test
(
'writes expected update task json'
,
()
async
{
_processResult
=
ProcessResult
(
1
,
0
,
commitSha
,
''
);
final
TaskResult
result
=
TaskResult
.
fromJson
(<
String
,
dynamic
>{
'success'
:
true
,
'data'
:
<
String
,
dynamic
>{
'i'
:
0
,
'j'
:
0
,
'not_a_metric'
:
'something'
,
},
'benchmarkScoreKeys'
:
<
String
>[
'i'
,
'j'
],
});
cocoon
=
Cocoon
(
fs:
fs
,
processRunSync:
runSyncStub
,
);
const
String
resultsPath
=
'results.json'
;
await
cocoon
.
writeTaskResultToFile
(
builderName:
'builderAbc'
,
gitBranch:
'master'
,
result:
result
,
resultsPath:
resultsPath
,
);
final
String
resultJson
=
fs
.
file
(
resultsPath
).
readAsStringSync
();
const
String
expectedJson
=
'{'
'"CommitBranch":"master",'
'"CommitSha":"
$commitSha
",'
'"BuilderName":"builderAbc",'
'"NewStatus":"Succeeded",'
'"ResultData":{"i":0.0,"j":0.0,"not_a_metric":"something"},'
'"BenchmarkScoreKeys":["i","j"]}'
;
expect
(
resultJson
,
expectedJson
);
});
test
(
'uploads expected update task payload from results file'
,
()
async
{
_processResult
=
ProcessResult
(
1
,
0
,
commitSha
,
''
);
cocoon
=
Cocoon
(
fs:
fs
,
httpClient:
mockClient
,
processRunSync:
runSyncStub
,
serviceAccountTokenPath:
serviceAccountTokenPath
,
);
const
String
resultsPath
=
'results.json'
;
const
String
updateTaskJson
=
'{'
'"CommitBranch":"master",'
'"CommitSha":"
$commitSha
",'
'"BuilderName":"builderAbc",'
'"NewStatus":"Succeeded",'
'"ResultData":{"i":0.0,"j":0.0,"not_a_metric":"something"},'
'"BenchmarkScoreKeys":["i","j"]}'
;
fs
.
file
(
resultsPath
).
writeAsStringSync
(
updateTaskJson
);
await
cocoon
.
sendResultsPath
(
resultsPath
);
});
test
(
'sends expected request from successful task'
,
()
async
{
mockClient
=
MockClient
((
Request
request
)
async
=>
Response
(
'{}'
,
200
));
cocoon
=
Cocoon
(
serviceAccountTokenPath:
serviceAccountTokenPath
,
f
ilesystem
:
fs
,
f
s
:
fs
,
httpClient:
mockClient
,
);
...
...
@@ -87,12 +144,13 @@ void main() {
cocoon
=
Cocoon
(
serviceAccountTokenPath:
serviceAccountTokenPath
,
f
ilesystem
:
fs
,
f
s
:
fs
,
httpClient:
mockClient
,
);
final
TaskResult
result
=
TaskResult
.
success
(<
String
,
dynamic
>{});
expect
(()
=>
cocoon
.
sendTaskResult
(
builderName:
'builderAbc'
,
gitBranch:
'branchAbc'
,
result:
result
),
throwsA
(
isA
<
ClientException
>()));
expect
(()
=>
cocoon
.
sendTaskResult
(
builderName:
'builderAbc'
,
gitBranch:
'branchAbc'
,
result:
result
),
throwsA
(
isA
<
ClientException
>()));
});
test
(
'null git branch throws error'
,
()
async
{
...
...
@@ -100,12 +158,13 @@ void main() {
cocoon
=
Cocoon
(
serviceAccountTokenPath:
serviceAccountTokenPath
,
f
ilesystem
:
fs
,
f
s
:
fs
,
httpClient:
mockClient
,
);
final
TaskResult
result
=
TaskResult
.
success
(<
String
,
dynamic
>{});
expect
(()
=>
cocoon
.
sendTaskResult
(
builderName:
'builderAbc'
,
gitBranch:
null
,
result:
result
),
throwsA
(
isA
<
AssertionError
>()));
expect
(()
=>
cocoon
.
sendTaskResult
(
builderName:
'builderAbc'
,
gitBranch:
null
,
result:
result
),
throwsA
(
isA
<
AssertionError
>()));
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment