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
9d364043
Unverified
Commit
9d364043
authored
Apr 26, 2019
by
Jonah Williams
Committed by
GitHub
Apr 26, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor the test compiler into a separate library (#31642)
parent
d121df99
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
311 additions
and
196 deletions
+311
-196
flutter_platform.dart
packages/flutter_tools/lib/src/test/flutter_platform.dart
+42
-196
test_compiler.dart
packages/flutter_tools/lib/src/test/test_compiler.dart
+184
-0
test_compiler_test.dart
packages/flutter_tools/test/test_compiler_test.dart
+85
-0
No files found.
packages/flutter_tools/lib/src/test/flutter_platform.dart
View file @
9d364043
...
@@ -16,21 +16,17 @@ import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_impo
...
@@ -16,21 +16,17 @@ import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_impo
import
'package:test_core/src/runner/plugin/platform_helpers.dart'
;
// ignore: implementation_imports
import
'package:test_core/src/runner/plugin/platform_helpers.dart'
;
// ignore: implementation_imports
import
'package:test_core/src/runner/environment.dart'
;
// ignore: implementation_imports
import
'package:test_core/src/runner/environment.dart'
;
// ignore: implementation_imports
import
'../artifacts.dart'
;
import
'../base/common.dart'
;
import
'../base/common.dart'
;
import
'../base/file_system.dart'
;
import
'../base/file_system.dart'
;
import
'../base/io.dart'
;
import
'../base/io.dart'
;
import
'../base/process_manager.dart'
;
import
'../base/process_manager.dart'
;
import
'../base/terminal.dart'
;
import
'../build_info.dart'
;
import
'../bundle.dart'
;
import
'../codegen.dart'
;
import
'../compile.dart'
;
import
'../compile.dart'
;
import
'../convert.dart'
;
import
'../convert.dart'
;
import
'../dart/package_map.dart'
;
import
'../dart/package_map.dart'
;
import
'../globals.dart'
;
import
'../globals.dart'
;
import
'../project.dart'
;
import
'../project.dart'
;
import
'../vmservice.dart'
;
import
'../vmservice.dart'
;
import
'test_compiler.dart'
;
import
'watcher.dart'
;
import
'watcher.dart'
;
/// The timeout we give the test process to connect to the test harness
/// The timeout we give the test process to connect to the test harness
...
@@ -231,156 +227,11 @@ void main() {
...
@@ -231,156 +227,11 @@ void main() {
return buffer.toString();
return buffer.toString();
}
}
enum _InitialResult { crashed, timedOut, connected }
enum InitialResult { crashed, timedOut, connected }
enum _TestResult { crashed, harnessBailed, testBailed }
typedef _Finalizer = Future<void> Function();
class _CompilationRequest {
enum TestResult { crashed, harnessBailed, testBailed }
_CompilationRequest(this.path, this.result);
String path;
Completer<String> result;
}
// This class is a wrapper around compiler that allows multiple isolates to
// enqueue compilation requests, but ensures only one compilation at a time.
class _Compiler {
_Compiler(bool trackWidgetCreation, Uri projectRootDirectory, FlutterProject flutterProject) {
// Compiler maintains and updates single incremental dill file.
// Incremental compilation requests done for each test copy that file away
// for independent execution.
final Directory outputDillDirectory = fs.systemTempDirectory.createTempSync('
flutter_test_compiler
.
');
final File outputDill = outputDillDirectory.childFile('
output
.
dill
');
printTrace('
Compiler
will
use
the
following
file
as
its
incremental
dill
file:
$
{
outputDill
.
path
}
');
bool suppressOutput = false;
void reportCompilerMessage(String message, {bool emphasis, TerminalColor color}) {
if (suppressOutput) {
return;
}
if (message.startsWith('
Error:
Could
not
resolve
the
package
\
'flutter_test
\'
'
))
{
printTrace
(
message
);
printError
(
'
\n\n
Failed to load test harness. Are you missing a dependency on flutter_test?
\n
'
,
emphasis:
emphasis
,
color:
color
,
);
suppressOutput
=
true
;
return
;
}
printError
(
'
$message
'
);
}
final
String
testFilePath
=
getKernelPathForTransformerOptions
(
fs
.
path
.
join
(
fs
.
path
.
fromUri
(
projectRootDirectory
),
getBuildDirectory
(),
'testfile.dill'
),
trackWidgetCreation:
trackWidgetCreation
,
);
Future
<
ResidentCompiler
>
createCompiler
()
async
{
if
(
flutterProject
.
hasBuilders
)
{
return
CodeGeneratingResidentCompiler
.
create
(
flutterProject:
flutterProject
,
trackWidgetCreation:
trackWidgetCreation
,
compilerMessageConsumer:
reportCompilerMessage
,
initializeFromDill:
testFilePath
,
// We already ran codegen once at the start, we only need to
// configure builders.
runCold:
true
,
);
}
return
ResidentCompiler
(
artifacts
.
getArtifactPath
(
Artifact
.
flutterPatchedSdkPath
),
packagesPath:
PackageMap
.
globalPackagesPath
,
trackWidgetCreation:
trackWidgetCreation
,
compilerMessageConsumer:
reportCompilerMessage
,
initializeFromDill:
testFilePath
,
unsafePackageSerialization:
false
,
);
}
printTrace
(
'Listening to compiler controller...'
);
compilerController
.
stream
.
listen
((
_CompilationRequest
request
)
async
{
final
bool
isEmpty
=
compilationQueue
.
isEmpty
;
compilationQueue
.
add
(
request
);
// Only trigger processing if queue was empty - i.e. no other requests
// are currently being processed. This effectively enforces "one
// compilation request at a time".
if
(
isEmpty
)
{
while
(
compilationQueue
.
isNotEmpty
)
{
final
_CompilationRequest
request
=
compilationQueue
.
first
;
printTrace
(
'Compiling
${request.path}
'
);
final
Stopwatch
compilerTime
=
Stopwatch
()..
start
();
bool
firstCompile
=
false
;
if
(
compiler
==
null
)
{
compiler
=
await
createCompiler
();
firstCompile
=
true
;
}
suppressOutput
=
false
;
final
CompilerOutput
compilerOutput
=
await
compiler
.
recompile
(
request
.
path
,
<
Uri
>[
Uri
.
parse
(
request
.
path
)],
outputPath:
outputDill
.
path
,
);
final
String
outputPath
=
compilerOutput
?.
outputFilename
;
// In case compiler didn't produce output or reported compilation
// errors, pass [null] upwards to the consumer and shutdown the
// compiler to avoid reusing compiler that might have gotten into
// a weird state.
if
(
outputPath
==
null
||
compilerOutput
.
errorCount
>
0
)
{
request
.
result
.
complete
(
null
);
await
_shutdown
();
}
else
{
final
File
outputFile
=
fs
.
file
(
outputPath
);
final
File
kernelReadyToRun
=
await
outputFile
.
copy
(
'
${request.path}
.dill'
);
final
File
testCache
=
fs
.
file
(
testFilePath
);
if
(
firstCompile
||
!
testCache
.
existsSync
()
||
(
testCache
.
lengthSync
()
<
outputFile
.
lengthSync
()))
{
// The idea is to keep the cache file up-to-date and include as
// much as possible in an effort to re-use as many packages as
// possible.
ensureDirectoryExists
(
testFilePath
);
await
outputFile
.
copy
(
testFilePath
);
}
request
.
result
.
complete
(
kernelReadyToRun
.
path
);
compiler
.
accept
();
compiler
.
reset
();
}
printTrace
(
'Compiling
${request.path}
took
${compilerTime.elapsedMilliseconds}
ms'
);
// Only remove now when we finished processing the element
compilationQueue
.
removeAt
(
0
);
}
}
},
onDone:
()
{
printTrace
(
'Deleting
${outputDillDirectory.path}
...'
);
outputDillDirectory
.
deleteSync
(
recursive:
true
);
});
}
final
StreamController
<
_CompilationRequest
>
compilerController
=
StreamController
<
_CompilationRequest
>();
typedef Finalizer = Future<void> Function();
final
List
<
_CompilationRequest
>
compilationQueue
=
<
_CompilationRequest
>[];
ResidentCompiler
compiler
;
Future
<
String
>
compile
(
String
mainDart
)
{
final
Completer
<
String
>
completer
=
Completer
<
String
>();
compilerController
.
add
(
_CompilationRequest
(
mainDart
,
completer
));
return
completer
.
future
;
}
Future
<
void
>
_shutdown
()
async
{
// Check for null in case this instance is shut down before the
// lazily-created compiler has been created.
if
(
compiler
!=
null
)
{
await
compiler
.
shutdown
();
compiler
=
null
;
}
}
Future
<
void
>
dispose
()
async
{
await
_shutdown
();
await
compilerController
.
close
();
}
}
/// The flutter test platform used to integrate with package:test.
/// The flutter test platform used to integrate with package:test.
class FlutterPlatform extends PlatformPlugin {
class FlutterPlatform extends PlatformPlugin {
...
@@ -423,7 +274,12 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -423,7 +274,12 @@ class FlutterPlatform extends PlatformPlugin {
final String icudtlPath;
final String icudtlPath;
Directory fontsDirectory;
Directory fontsDirectory;
_Compiler
compiler
;
/// The test compiler produces dill files for each test main.
///
/// To speed up compilation, each compile is intialized from an existing
/// dill file from previous runs, if possible.
TestCompiler compiler;
// Each time loadChannel() is called, we spin up a local WebSocket server,
// Each time loadChannel() is called, we spin up a local WebSocket server,
// then spin up the engine in a subprocess. We pass the engine a Dart file
// then spin up the engine in a subprocess. We pass the engine a Dart file
...
@@ -525,11 +381,9 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -525,11 +381,9 @@ class FlutterPlatform extends PlatformPlugin {
)
async
{
)
async
{
printTrace
(
'test
$ourTestCount
: starting test
$testPath
'
);
printTrace
(
'test
$ourTestCount
: starting test
$testPath
'
);
_AsyncError
_AsyncError
outOfBandError
;
// error that we couldn't send to the harness that we need to send via our future
outOfBandError
;
// error that we couldn't send to the harness that we need to send via our future
final
List
<
_Finalizer
>
finalizers
=
final
List
<
Finalizer
>
finalizers
=
<
Finalizer
>[];
// Will be run in reverse order.
<
_Finalizer
>[];
// Will be run in reverse order.
bool
subprocessActive
=
false
;
bool
subprocessActive
=
false
;
bool
controllerSinkClosed
=
false
;
bool
controllerSinkClosed
=
false
;
try
{
try
{
...
@@ -545,10 +399,9 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -545,10 +399,9 @@ class FlutterPlatform extends PlatformPlugin {
await
server
.
close
(
force:
true
);
await
server
.
close
(
force:
true
);
});
});
final
Completer
<
WebSocket
>
webSocket
=
Completer
<
WebSocket
>();
final
Completer
<
WebSocket
>
webSocket
=
Completer
<
WebSocket
>();
server
.
listen
(
server
.
listen
((
HttpRequest
request
)
{
(
HttpRequest
request
)
{
if
(!
webSocket
.
isCompleted
)
if
(!
webSocket
.
isCompleted
)
webSocket
.
complete
(
WebSocketTransformer
.
upgrade
(
request
));
webSocket
.
complete
(
WebSocketTransformer
.
upgrade
(
request
));
},
},
onError:
(
dynamic
error
,
dynamic
stack
)
{
onError:
(
dynamic
error
,
dynamic
stack
)
{
// If you reach here, it's unlikely we're going to be able to really handle this well.
// If you reach here, it's unlikely we're going to be able to really handle this well.
...
@@ -574,12 +427,11 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -574,12 +427,11 @@ class FlutterPlatform extends PlatformPlugin {
}
else
if
(
precompiledDillFiles
!=
null
)
{
}
else
if
(
precompiledDillFiles
!=
null
)
{
mainDart
=
precompiledDillFiles
[
testPath
];
mainDart
=
precompiledDillFiles
[
testPath
];
}
}
mainDart
??=
mainDart
??=
_createListenerDart
(
finalizers
,
ourTestCount
,
testPath
,
server
);
_createListenerDart
(
finalizers
,
ourTestCount
,
testPath
,
server
);
if
(
precompiledDillPath
==
null
&&
precompiledDillFiles
==
null
)
{
if
(
precompiledDillPath
==
null
&&
precompiledDillFiles
==
null
)
{
// Lazily instantiate compiler so it is built only if it is actually used.
// Lazily instantiate compiler so it is built only if it is actually used.
compiler
??=
_Compiler
(
trackWidgetCreation
,
projectRootDirectory
,
flutterProject
);
compiler
??=
TestCompiler
(
trackWidgetCreation
,
flutterProject
);
mainDart
=
await
compiler
.
compile
(
mainDart
);
mainDart
=
await
compiler
.
compile
(
mainDart
);
if
(
mainDart
==
null
)
{
if
(
mainDart
==
null
)
{
...
@@ -655,7 +507,7 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -655,7 +507,7 @@ class FlutterPlatform extends PlatformPlugin {
ProcessEvent
(
ourTestCount
,
process
,
processObservatoryUri
));
ProcessEvent
(
ourTestCount
,
process
,
processObservatoryUri
));
},
},
startTimeoutTimer:
()
{
startTimeoutTimer:
()
{
Future
<
_
InitialResult
>.
delayed
(
_kTestStartupTimeout
)
Future
<
InitialResult
>.
delayed
(
_kTestStartupTimeout
)
.
then
<
void
>((
_
)
=>
timeout
.
complete
());
.
then
<
void
>((
_
)
=>
timeout
.
complete
());
},
},
);
);
...
@@ -664,25 +516,20 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -664,25 +516,20 @@ class FlutterPlatform extends PlatformPlugin {
// The engine could crash, in which case process.exitCode will complete.
// The engine could crash, in which case process.exitCode will complete.
// The engine could connect to us, in which case webSocket.future will complete.
// The engine could connect to us, in which case webSocket.future will complete.
// The local test harness could get bored of us.
// The local test harness could get bored of us.
printTrace
(
'test
$ourTestCount
: awaiting initial result for pid
${process.pid}
'
);
printTrace
(
'test
$ourTestCount
: awaiting initial result for pid
${process.pid}
'
);
final
_InitialResult
initialResult
=
final
InitialResult
initialResult
=
await
Future
.
any
<
InitialResult
>(<
Future
<
InitialResult
>>[
await
Future
.
any
<
_InitialResult
>(<
Future
<
_InitialResult
>>[
process
.
exitCode
.
then
<
InitialResult
>((
int
exitCode
)
=>
InitialResult
.
crashed
),
process
.
exitCode
timeout
.
future
.
then
<
InitialResult
>((
void
value
)
=>
InitialResult
.
timedOut
),
.
then
<
_InitialResult
>((
int
exitCode
)
=>
_InitialResult
.
crashed
),
Future
<
InitialResult
>.
delayed
(
_kTestProcessTimeout
,
()
=>
InitialResult
.
timedOut
),
timeout
.
future
gotProcessObservatoryUri
.
future
.
then
<
InitialResult
>((
void
value
)
{
.
then
<
_InitialResult
>((
void
value
)
=>
_InitialResult
.
timedOut
),
return
webSocket
.
future
.
then
<
InitialResult
>(
Future
<
_InitialResult
>.
delayed
(
(
WebSocket
webSocket
)
=>
InitialResult
.
connected
,
_kTestProcessTimeout
,
()
=>
_InitialResult
.
timedOut
),
gotProcessObservatoryUri
.
future
.
then
<
_InitialResult
>((
void
value
)
{
return
webSocket
.
future
.
then
<
_InitialResult
>(
(
WebSocket
webSocket
)
=>
_InitialResult
.
connected
,
);
);
}),
}),
]);
]);
switch
(
initialResult
)
{
switch
(
initialResult
)
{
case
_
InitialResult
.
crashed
:
case
InitialResult
.
crashed
:
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
crashed before connecting to test harness'
);
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
crashed before connecting to test harness'
);
final
int
exitCode
=
await
process
.
exitCode
;
final
int
exitCode
=
await
process
.
exitCode
;
subprocessActive
=
false
;
subprocessActive
=
false
;
...
@@ -698,7 +545,7 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -698,7 +545,7 @@ class FlutterPlatform extends PlatformPlugin {
await
controller
.
sink
.
done
;
await
controller
.
sink
.
done
;
await
watcher
?.
handleTestCrashed
(
ProcessEvent
(
ourTestCount
,
process
));
await
watcher
?.
handleTestCrashed
(
ProcessEvent
(
ourTestCount
,
process
));
break
;
break
;
case
_
InitialResult
.
timedOut
:
case
InitialResult
.
timedOut
:
// Could happen either if the process takes a long time starting
// Could happen either if the process takes a long time starting
// (_kTestProcessTimeout), or if once Dart code starts running, it takes a
// (_kTestProcessTimeout), or if once Dart code starts running, it takes a
// long time to open the WebSocket connection (_kTestStartupTimeout).
// long time to open the WebSocket connection (_kTestStartupTimeout).
...
@@ -712,7 +559,7 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -712,7 +559,7 @@ class FlutterPlatform extends PlatformPlugin {
await
watcher
await
watcher
?.
handleTestTimedOut
(
ProcessEvent
(
ourTestCount
,
process
));
?.
handleTestTimedOut
(
ProcessEvent
(
ourTestCount
,
process
));
break
;
break
;
case
_
InitialResult
.
connected
:
case
InitialResult
.
connected
:
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
connected to test harness'
);
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
connected to test harness'
);
final
WebSocket
testSocket
=
await
webSocket
.
future
;
final
WebSocket
testSocket
=
await
webSocket
.
future
;
...
@@ -758,16 +605,15 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -758,16 +605,15 @@ class FlutterPlatform extends PlatformPlugin {
);
);
printTrace
(
'test
$ourTestCount
: awaiting test result for pid
${process.pid}
'
);
printTrace
(
'test
$ourTestCount
: awaiting test result for pid
${process.pid}
'
);
final
_TestResult
testResult
=
final
TestResult
testResult
=
await
Future
.
any
<
TestResult
>(<
Future
<
TestResult
>>[
await
Future
.
any
<
_TestResult
>(<
Future
<
_TestResult
>>[
process
.
exitCode
.
then
<
TestResult
>((
int
exitCode
)
{
process
.
exitCode
.
then
<
_TestResult
>((
int
exitCode
)
{
return
TestResult
.
crashed
;
return
_TestResult
.
crashed
;
}),
}),
harnessDone
.
future
.
then
<
_
TestResult
>((
void
value
)
{
harnessDone
.
future
.
then
<
TestResult
>((
void
value
)
{
return
_
TestResult
.
harnessBailed
;
return
TestResult
.
harnessBailed
;
}),
}),
testDone
.
future
.
then
<
_
TestResult
>((
void
value
)
{
testDone
.
future
.
then
<
TestResult
>((
void
value
)
{
return
_
TestResult
.
testBailed
;
return
TestResult
.
testBailed
;
}),
}),
]);
]);
...
@@ -777,7 +623,7 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -777,7 +623,7 @@ class FlutterPlatform extends PlatformPlugin {
]);
]);
switch
(
testResult
)
{
switch
(
testResult
)
{
case
_
TestResult
.
crashed
:
case
TestResult
.
crashed
:
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
crashed'
);
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
crashed'
);
final
int
exitCode
=
await
process
.
exitCode
;
final
int
exitCode
=
await
process
.
exitCode
;
subprocessActive
=
false
;
subprocessActive
=
false
;
...
@@ -792,12 +638,12 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -792,12 +638,12 @@ class FlutterPlatform extends PlatformPlugin {
printTrace
(
'test
$ourTestCount
: waiting for controller sink to close'
);
printTrace
(
'test
$ourTestCount
: waiting for controller sink to close'
);
await
controller
.
sink
.
done
;
await
controller
.
sink
.
done
;
break
;
break
;
case
_
TestResult
.
harnessBailed
:
case
TestResult
.
harnessBailed
:
case
_
TestResult
.
testBailed
:
case
TestResult
.
testBailed
:
if
(
testResult
==
_
TestResult
.
harnessBailed
)
{
if
(
testResult
==
TestResult
.
harnessBailed
)
{
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
no longer needed by test harness'
);
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
no longer needed by test harness'
);
}
else
{
}
else
{
assert
(
testResult
==
_
TestResult
.
testBailed
);
assert
(
testResult
==
TestResult
.
testBailed
);
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
no longer needs test harness'
);
printTrace
(
'test
$ourTestCount
: process with pid
${process.pid}
no longer needs test harness'
);
}
}
await
watcher
?.
handleFinishedTest
(
await
watcher
?.
handleFinishedTest
(
...
@@ -817,7 +663,7 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -817,7 +663,7 @@ class FlutterPlatform extends PlatformPlugin {
}
finally
{
}
finally
{
printTrace
(
'test
$ourTestCount
: cleaning up...'
);
printTrace
(
'test
$ourTestCount
: cleaning up...'
);
// Finalizers are treated like a stack; run them in reverse order.
// Finalizers are treated like a stack; run them in reverse order.
for
(
_
Finalizer
finalizer
in
finalizers
.
reversed
)
{
for
(
Finalizer
finalizer
in
finalizers
.
reversed
)
{
try
{
try
{
await
finalizer
();
await
finalizer
();
}
catch
(
error
,
stack
)
{
}
catch
(
error
,
stack
)
{
...
@@ -848,7 +694,7 @@ class FlutterPlatform extends PlatformPlugin {
...
@@ -848,7 +694,7 @@ class FlutterPlatform extends PlatformPlugin {
}
}
String
_createListenerDart
(
String
_createListenerDart
(
List
<
_
Finalizer
>
finalizers
,
List
<
Finalizer
>
finalizers
,
int
ourTestCount
,
int
ourTestCount
,
String
testPath
,
String
testPath
,
HttpServer
server
,
HttpServer
server
,
...
...
packages/flutter_tools/lib/src/test/test_compiler.dart
0 → 100644
View file @
9d364043
// Copyright 2019 The Chromium 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:async'
;
import
'package:meta/meta.dart'
;
import
'../artifacts.dart'
;
import
'../base/file_system.dart'
;
import
'../base/terminal.dart'
;
import
'../build_info.dart'
;
import
'../bundle.dart'
;
import
'../codegen.dart'
;
import
'../compile.dart'
;
import
'../dart/package_map.dart'
;
import
'../globals.dart'
;
import
'../project.dart'
;
/// A request to the [TestCompiler] for recompilation.
class
_CompilationRequest
{
_CompilationRequest
(
this
.
path
,
this
.
result
);
String
path
;
Completer
<
String
>
result
;
}
/// A frontend_server wrapper for the flutter test runner.
///
/// This class is a wrapper around compiler that allows multiple isolates to
/// enqueue compilation requests, but ensures only one compilation at a time.
class
TestCompiler
{
/// Creates a new [TestCompiler] which acts as a frontend_server proxy.
///
/// [trackWidgetCreation] configures whether the kernel transform is applied
/// to the output. This also changes the output file to include a '.track`
/// extension.
///
/// [flutterProject] is the project for which we are running tests.
TestCompiler
(
this
.
trackWidgetCreation
,
this
.
flutterProject
,
)
:
testFilePath
=
getKernelPathForTransformerOptions
(
fs
.
path
.
join
(
flutterProject
.
directory
.
path
,
getBuildDirectory
(),
'testfile.dill'
),
trackWidgetCreation:
trackWidgetCreation
,
)
{
// Compiler maintains and updates single incremental dill file.
// Incremental compilation requests done for each test copy that file away
// for independent execution.
final
Directory
outputDillDirectory
=
fs
.
systemTempDirectory
.
createTempSync
(
'flutter_test_compiler.'
);
outputDill
=
outputDillDirectory
.
childFile
(
'output.dill'
);
printTrace
(
'Compiler will use the following file as its incremental dill file:
${outputDill.path}
'
);
printTrace
(
'Listening to compiler controller...'
);
compilerController
.
stream
.
listen
(
_onCompilationRequest
,
onDone:
()
{
printTrace
(
'Deleting
${outputDillDirectory.path}
...'
);
outputDillDirectory
.
deleteSync
(
recursive:
true
);
});
}
final
StreamController
<
_CompilationRequest
>
compilerController
=
StreamController
<
_CompilationRequest
>();
final
List
<
_CompilationRequest
>
compilationQueue
=
<
_CompilationRequest
>[];
final
FlutterProject
flutterProject
;
final
bool
trackWidgetCreation
;
final
String
testFilePath
;
ResidentCompiler
compiler
;
File
outputDill
;
// Whether to report compiler messages.
bool
_suppressOutput
=
false
;
Future
<
String
>
compile
(
String
mainDart
)
{
final
Completer
<
String
>
completer
=
Completer
<
String
>();
compilerController
.
add
(
_CompilationRequest
(
mainDart
,
completer
));
return
completer
.
future
;
}
Future
<
void
>
_shutdown
()
async
{
// Check for null in case this instance is shut down before the
// lazily-created compiler has been created.
if
(
compiler
!=
null
)
{
await
compiler
.
shutdown
();
compiler
=
null
;
}
}
Future
<
void
>
dispose
()
async
{
await
compilerController
.
close
();
}
/// Create the resident compiler used to compile the test.
@visibleForTesting
Future
<
ResidentCompiler
>
createCompiler
()
async
{
if
(
flutterProject
.
hasBuilders
)
{
return
CodeGeneratingResidentCompiler
.
create
(
flutterProject:
flutterProject
,
trackWidgetCreation:
trackWidgetCreation
,
compilerMessageConsumer:
_reportCompilerMessage
,
initializeFromDill:
testFilePath
,
// We already ran codegen once at the start, we only need to
// configure builders.
runCold:
true
,
);
}
return
ResidentCompiler
(
artifacts
.
getArtifactPath
(
Artifact
.
flutterPatchedSdkPath
),
packagesPath:
PackageMap
.
globalPackagesPath
,
trackWidgetCreation:
trackWidgetCreation
,
compilerMessageConsumer:
_reportCompilerMessage
,
initializeFromDill:
testFilePath
,
unsafePackageSerialization:
false
,
);
}
// Handle a compilation request.
Future
<
void
>
_onCompilationRequest
(
_CompilationRequest
request
)
async
{
final
bool
isEmpty
=
compilationQueue
.
isEmpty
;
compilationQueue
.
add
(
request
);
// Only trigger processing if queue was empty - i.e. no other requests
// are currently being processed. This effectively enforces "one
// compilation request at a time".
if
(!
isEmpty
)
{
return
;
}
while
(
compilationQueue
.
isNotEmpty
)
{
final
_CompilationRequest
request
=
compilationQueue
.
first
;
printTrace
(
'Compiling
${request.path}
'
);
final
Stopwatch
compilerTime
=
Stopwatch
()..
start
();
bool
firstCompile
=
false
;
if
(
compiler
==
null
)
{
compiler
=
await
createCompiler
();
firstCompile
=
true
;
}
_suppressOutput
=
false
;
final
CompilerOutput
compilerOutput
=
await
compiler
.
recompile
(
request
.
path
,
<
Uri
>[
Uri
.
parse
(
request
.
path
)],
outputPath:
outputDill
.
path
,
);
final
String
outputPath
=
compilerOutput
?.
outputFilename
;
// In case compiler didn't produce output or reported compilation
// errors, pass [null] upwards to the consumer and shutdown the
// compiler to avoid reusing compiler that might have gotten into
// a weird state.
if
(
outputPath
==
null
||
compilerOutput
.
errorCount
>
0
)
{
request
.
result
.
complete
(
null
);
await
_shutdown
();
}
else
{
final
File
outputFile
=
fs
.
file
(
outputPath
);
final
File
kernelReadyToRun
=
await
outputFile
.
copy
(
'
${request.path}
.dill'
);
final
File
testCache
=
fs
.
file
(
testFilePath
);
if
(
firstCompile
||
!
testCache
.
existsSync
()
||
(
testCache
.
lengthSync
()
<
outputFile
.
lengthSync
()))
{
// The idea is to keep the cache file up-to-date and include as
// much as possible in an effort to re-use as many packages as
// possible.
ensureDirectoryExists
(
testFilePath
);
await
outputFile
.
copy
(
testFilePath
);
}
request
.
result
.
complete
(
kernelReadyToRun
.
path
);
compiler
.
accept
();
compiler
.
reset
();
}
printTrace
(
'Compiling
${request.path}
took
${compilerTime.elapsedMilliseconds}
ms'
);
// Only remove now when we finished processing the element
compilationQueue
.
removeAt
(
0
);
}
}
void
_reportCompilerMessage
(
String
message
,
{
bool
emphasis
,
TerminalColor
color
})
{
if
(
_suppressOutput
)
{
return
;
}
if
(
message
.
startsWith
(
'Error: Could not resolve the package
\'
flutter_test
\'
'
))
{
printTrace
(
message
);
printError
(
'
\n\n
Failed to load test harness. Are you missing a dependency on flutter_test?
\n
'
,
emphasis:
emphasis
,
color:
color
,
);
_suppressOutput
=
true
;
return
;
}
printError
(
'
$message
'
);
}
}
packages/flutter_tools/test/test_compiler_test.dart
0 → 100644
View file @
9d364043
// Copyright 2019 The Chromium 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:file/memory.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/compile.dart'
;
import
'package:flutter_tools/src/project.dart'
;
import
'package:flutter_tools/src/test/test_compiler.dart'
;
import
'package:mockito/mockito.dart'
;
import
'src/common.dart'
;
import
'src/context.dart'
;
void
main
(
)
{
group
(
'TestCompiler'
,
()
{
testUsingContext
(
'compiles test file with no errors'
,
()
async
{
fs
.
file
(
'pubspec.yaml'
).
createSync
();
fs
.
file
(
'.packages'
).
createSync
();
fs
.
file
(
'test/foo.dart'
).
createSync
(
recursive:
true
);
final
MockResidentCompiler
residentCompiler
=
MockResidentCompiler
();
final
TestCompiler
testCompiler
=
FakeTestCompiler
(
false
,
await
FlutterProject
.
current
(),
residentCompiler
,
);
when
(
residentCompiler
.
recompile
(
'test/foo.dart'
,
<
Uri
>[
Uri
.
parse
(
'test/foo.dart'
)],
outputPath:
testCompiler
.
outputDill
.
path
,
)).
thenAnswer
((
Invocation
invocation
)
async
{
fs
.
file
(
'abc.dill'
).
createSync
();
return
CompilerOutput
(
'abc.dill'
,
0
,
<
Uri
>[]);
});
expect
(
await
testCompiler
.
compile
(
'test/foo.dart'
),
'test/foo.dart.dill'
);
expect
(
fs
.
file
(
'test/foo.dart.dill'
).
existsSync
(),
true
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
(),
});
testUsingContext
(
'does not compile test file with errors'
,
()
async
{
fs
.
file
(
'pubspec.yaml'
).
createSync
();
fs
.
file
(
'.packages'
).
createSync
();
fs
.
file
(
'test/foo.dart'
).
createSync
(
recursive:
true
);
final
MockResidentCompiler
residentCompiler
=
MockResidentCompiler
();
final
TestCompiler
testCompiler
=
FakeTestCompiler
(
false
,
await
FlutterProject
.
current
(),
residentCompiler
,
);
when
(
residentCompiler
.
recompile
(
'test/foo.dart'
,
<
Uri
>[
Uri
.
parse
(
'test/foo.dart'
)],
outputPath:
testCompiler
.
outputDill
.
path
,
)).
thenAnswer
((
Invocation
invocation
)
async
{
fs
.
file
(
'abc.dill'
).
createSync
();
return
CompilerOutput
(
'abc.dill'
,
1
,
<
Uri
>[]);
});
expect
(
await
testCompiler
.
compile
(
'test/foo.dart'
),
null
);
expect
(
fs
.
file
(
'test/foo.dart.dill'
).
existsSync
(),
false
);
},
overrides:
<
Type
,
Generator
>{
FileSystem:
()
=>
MemoryFileSystem
(),
});
});
}
/// Override the creation of the Resident Compiler to simplify testing.
class
FakeTestCompiler
extends
TestCompiler
{
FakeTestCompiler
(
bool
trackWidgetCreation
,
FlutterProject
flutterProject
,
this
.
residentCompiler
,
)
:
super
(
trackWidgetCreation
,
flutterProject
);
final
MockResidentCompiler
residentCompiler
;
@override
Future
<
ResidentCompiler
>
createCompiler
()
async
{
return
residentCompiler
;
}
}
class
MockResidentCompiler
extends
Mock
implements
ResidentCompiler
{}
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