diff --git a/pubspec.yaml b/pubspec.yaml index e46f0cee..f2c88702 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,11 @@ dependencies: stream_channel: ">=1.6.0 <3.0.0" dev_dependencies: + cli_pkg: ^1.0.0 grinder: ^0.8.0 protoc_plugin: ^19.0.0 path: ^1.6.0 test: ^1.0.0 + +dependency_overrides: + cli_pkg: {git: git://github.com/google/dart_cli_pkg} diff --git a/test/embedded_process.dart b/test/embedded_process.dart index 167c69d5..bb3a8d29 100644 --- a/test/embedded_process.dart +++ b/test/embedded_process.dart @@ -7,11 +7,15 @@ import 'dart:convert'; import 'dart:io'; import 'package:async/async.dart'; +import 'package:grinder/grinder.dart'; +import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:sass_embedded/src/embedded_sass.pb.dart'; import 'package:sass_embedded/src/util/length_delimited_transformer.dart'; +import 'utils.dart'; + /// A wrapper for [Process] that provides a convenient API for testing the /// embedded Sass process. /// @@ -77,9 +81,12 @@ class EmbeddedProcess { bool includeParentEnvironment = true, bool runInShell = false, bool forwardOutput = false}) async { - // TODO(nweiz): Support running from a native executable. + var scriptOrSnapshot = executablePath; var process = await Process.start( - Platform.executable, ["bin/dart_sass_embedded.dart"], + executablePath.endsWith(".native") + ? p.join(sdkDir.path, "bin", "dartaotruntime") + : Platform.executable, + [scriptOrSnapshot], workingDirectory: workingDirectory, environment: environment, includeParentEnvironment: includeParentEnvironment, diff --git a/test/protocol_test.dart b/test/protocol_test.dart index 6daab466..920dd8a7 100644 --- a/test/protocol_test.dart +++ b/test/protocol_test.dart @@ -10,6 +10,8 @@ import 'embedded_process.dart'; import 'utils.dart'; void main() { + ensureExecutableUpToDate(); + EmbeddedProcess process; setUp(() async { process = await EmbeddedProcess.start(); diff --git a/test/utils.dart b/test/utils.dart index cfe3ff96..f0c2e256 100644 --- a/test/utils.dart +++ b/test/utils.dart @@ -2,12 +2,91 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'dart:io'; + +import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'package:sass_embedded/src/embedded_sass.pb.dart'; import 'embedded_process.dart'; +/// Whether [ensureExecutableUpToDate] has been called. +var _ensuredExecutableUpToDate = false; + +/// Returns the path to the executable to execute. +/// +/// This may be a raw Dart executable, a script snapshot that ends in +/// `.snapshot`, or a native-code snapshot that ends in `.native`. +final String executablePath = () { + expect(_ensuredExecutableUpToDate, isTrue, + reason: + "ensureExecutableUpToDate() must be called at top of the test file."); + + var nativeSnapshot = "build/dart_sass_embedded.dart.native"; + if (File(nativeSnapshot).existsSync()) return nativeSnapshot; + + var bytecodeSnapshot = "build/dart_sass_embedded.dart.snapshot"; + if (File(bytecodeSnapshot).existsSync()) return bytecodeSnapshot; + + return "bin/dart_sass_embedded.dart"; +}(); + +/// Creates a [setUpAll] that verifies that the compiled form of the migrator +/// executable is up-to-date, if necessary. +/// +/// This should always be called before [runMigrator]. +void ensureExecutableUpToDate() { + setUpAll(() { + _ensuredExecutableUpToDate = true; + + if (!executablePath.endsWith(".dart")) { + _ensureUpToDate( + executablePath, + "pub run grinder protobuf " + "pkg-compile-${Platform.isWindows ? 'snapshot' : 'native'}"); + } + }); +} + +/// Ensures that [path] (usually a compilation artifact) has been modified more +/// recently than all this package's source files. +/// +/// If [path] isn't up-to-date, this throws an error encouraging the user to run +/// [commandToRun]. +void _ensureUpToDate(String path, String commandToRun) { + // Ensure path is relative so the error messages are more readable. + path = p.relative(path); + if (!File(path).existsSync()) { + throw "$path does not exist. Run $commandToRun."; + } + + var lastModified = File(path).lastModifiedSync(); + var entriesToCheck = Directory("lib").listSync(recursive: true).toList(); + + // If we have a dependency override, "pub run" will touch the lockfile to mark + // it as newer than the pubspec, which makes it unsuitable to use for + // freshness checking. + if (File("pubspec.yaml") + .readAsStringSync() + .contains("dependency_overrides")) { + entriesToCheck.add(File("pubspec.yaml")); + } else { + entriesToCheck.add(File("pubspec.lock")); + } + + for (var entry in entriesToCheck) { + if (entry is File) { + var entryLastModified = entry.lastModifiedSync(); + if (lastModified.isBefore(entryLastModified)) { + throw "${entry.path} was modified after ${p.prettyUri(p.toUri(path))} " + "was generated.\n" + "Run $commandToRun."; + } + } + } +} + /// Returns a [InboundMessage] that compiles the given plain CSS /// string. InboundMessage compileString(String css) => InboundMessage() diff --git a/tool/grind.dart b/tool/grind.dart index 27795f05..6b15916d 100644 --- a/tool/grind.dart +++ b/tool/grind.dart @@ -4,11 +4,15 @@ import 'dart:io'; +import 'package:cli_pkg/cli_pkg.dart' as pkg; import 'package:grinder/grinder.dart'; import 'utils.dart'; -main(List args) => grind(args); +main(List args) { + pkg.addGithubTasks(); + grind(args); +} @Task('Compile the protocol buffer definition to a Dart library.') protobuf() async {