blob: 33e1f4c3a6b4d487ce96a675f9b3af6dcf77c52c [file]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@TestOn('vm')
@Tags(['node'])
library;
import 'dart:convert';
import 'dart:io';
import 'package:test/src/runner/executable_settings.dart';
import 'package:test/test.dart';
import 'package:test_core/src/util/io.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import '../../io.dart';
final _success = '''
import 'package:test/test.dart';
void main() {
test("success", () {});
}
''';
final _failure = '''
import 'package:test/test.dart';
void main() {
test("failure", () => throw TestFailure("oh no"));
}
''';
({int major, String full})? _nodeVersion;
({int major, String full}) _readNodeVersion() {
final process = Process.runSync(
ExecutableSettings(
linuxExecutable: 'node',
macOSExecutable: 'node',
windowsExecutable: 'node.exe',
).executable,
['--version'],
stdoutEncoding: utf8,
);
if (process.exitCode != 0) {
throw const OSError('Could not run node --version');
}
final version = RegExp(r'v(\d+)\..*');
final parsed = version.firstMatch(process.stdout as String)!;
return (major: int.parse(parsed.group(1)!), full: process.stdout);
}
String? skipBelowMajorNodeVersion(int minimumMajorVersion) {
final (:major, :full) = _nodeVersion ??= _readNodeVersion();
if (major < minimumMajorVersion) {
return 'This test requires Node $minimumMajorVersion.x or later, '
'but is running on $full';
}
return null;
}
String? skipAboveMajorNodeVersion(int maximumMajorVersion) {
final (:major, :full) = _nodeVersion ??= _readNodeVersion();
if (major > maximumMajorVersion) {
return 'This test requires Node $maximumMajorVersion.x or older, '
'but is running on $full';
}
return null;
}
void main() {
setUpAll(precompileTestExecutable);
group('fails gracefully if', () {
test('a test file fails to compile', () async {
await d.file('test.dart', 'void main() {invalid Dart}').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(
test.stdout,
containsInOrder([
'Error: Compilation failed.',
'-1: loading test.dart [E]',
'Failed to load "test.dart": dart2js failed.',
]),
);
await test.shouldExit(1);
});
test('a test file throws', () async {
await d.file('test.dart', "void main() => throw 'oh no';").create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(
test.stdout,
containsInOrder([
'-1: loading test.dart [E]',
'Failed to load "test.dart": oh no',
]),
);
await test.shouldExit(1);
});
test("a test file doesn't have a main defined", () async {
await d.file('test.dart', 'void foo() {}').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(
test.stdout,
containsInOrder([
'-1: loading test.dart [E]',
'Failed to load "test.dart": No top-level main() function defined.',
]),
);
await test.shouldExit(1);
}, skip: 'https://github.com/dart-lang/test/issues/894');
test('a test file has a non-function main', () async {
await d.file('test.dart', 'int main;').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(
test.stdout,
containsInOrder([
'-1: loading test.dart [E]',
'Failed to load "test.dart": Top-level main getter is not a function.',
]),
);
await test.shouldExit(1);
}, skip: 'https://github.com/dart-lang/test/issues/894');
test('a test file has a main with arguments', () async {
await d.file('test.dart', 'void main(arg) {}').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(
test.stdout,
containsInOrder([
'-1: loading test.dart [E]',
'Failed to load "test.dart": Top-level main() function takes arguments.',
]),
);
await test.shouldExit(1);
});
});
group('runs successful tests', () {
test('on Node and the VM', () async {
await d.file('test.dart', _success).create();
var test = await runTest(['-p', 'node', '-p', 'vm', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
await test.shouldExit(0);
});
// Regression test; this broke in 0.12.0-beta.9.
test('on a file in a subdirectory', () async {
await d.dir('dir', [d.file('test.dart', _success)]).create();
var test = await runTest(['-p', 'node', 'dir/test.dart']);
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
await test.shouldExit(0);
});
test('compiled with dart2wasm', () async {
await d.file('test.dart', _success).create();
var test = await runTest([
'-p',
'node',
'--compiler',
'dart2wasm',
'test.dart',
]);
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
await test.shouldExit(0);
}, skip: skipBelowMajorNodeVersion(22));
});
test('defines a node environment constant', () async {
await d.file('test.dart', '''
import 'package:test/test.dart';
void main() {
test("test", () {
expect(const bool.fromEnvironment("node"), isTrue);
});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
await test.shouldExit(0);
});
test('runs failing tests that fail only on node', () async {
await d.file('test.dart', '''
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
void main() {
test("test", () {
if (const bool.fromEnvironment("node")) {
throw TestFailure("oh no");
}
});
}
''').create();
var test = await runTest([
'-p',
'node',
'-p',
'vm',
'-c',
'dart2js',
'test.dart',
]);
expect(test.stdout, emitsThrough(contains('+1 -1: Some tests failed.')));
await test.shouldExit(1);
});
test('runs failing tests that fail only on node (with dart2wasm)', () async {
await d.file('test.dart', '''
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
void main() {
test("test", () {
if (const bool.fromEnvironment("node")) {
throw TestFailure("oh no");
}
});
}
''').create();
var test = await runTest([
'-p',
'node',
'-p',
'vm',
'-c',
'dart2js',
'-c',
'dart2wasm',
'test.dart',
]);
expect(test.stdout, emitsThrough(contains('+1 -2: Some tests failed.')));
await test.shouldExit(1);
}, skip: skipBelowMajorNodeVersion(22));
test('gracefully handles wasm errors on old node versions', () async {
// Old Node.JS versions can't read the WebAssembly modules emitted by
// dart2wasm. The node process exits before connecting to the server
// opened by the test runner, leading to timeouts. So, this is a
// regression test for https://github.com/dart-lang/test/pull/2259#issuecomment-2307868442
await d.file('test.dart', '''
import 'package:test/test.dart';
void main() {
test("test", () {
// Should pass on newer node versions
});
}
''').create();
var test = await runTest(['-p', 'node', '-c', 'dart2wasm', 'test.dart']);
expect(
test.stdout,
emitsInOrder([
emitsThrough(
contains('Node exited before connecting to the test channel.'),
),
emitsThrough(contains('-1: Some tests failed.')),
]),
);
await test.shouldExit(1);
}, skip: skipAboveMajorNodeVersion(21));
test('forwards prints from the Node test', () async {
await d.file('test.dart', '''
import 'dart:async';
import 'package:test/test.dart';
void main() {
test("test", () {
print("Hello,");
return Future(() => print("world!"));
});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsInOrder([emitsThrough('Hello,'), 'world!']));
await test.shouldExit(0);
});
test('forwards raw JS prints from the Node test', () async {
await d.file('test.dart', '''
import 'dart:async';
import 'dart:js_interop';
import 'package:test/test.dart';
@JS('console.log')
external void log(JSString value);
void main() {
test('test', () {
log('Hello,'.toJS);
return Future(() => log('world!'.toJS));
});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsInOrder([emitsThrough('Hello,'), 'world!']));
await test.shouldExit(0);
});
test('dartifies stack traces for JS-compiled tests by default', () async {
await d.file('test.dart', _failure).create();
var test = await runTest(['-p', 'node', '--verbose-trace', 'test.dart']);
expect(
test.stdout,
containsInOrder([' main.<fn>', 'package:test', 'dart:async/zone.dart']),
skip: 'https://github.com/dart-lang/sdk/issues/41949',
);
await test.shouldExit(1);
});
test(
"doesn't dartify stack traces for JS-compiled tests with --js-trace",
() async {
await d.file('test.dart', _failure).create();
var test = await runTest([
'-p',
'node',
'--verbose-trace',
'--js-trace',
'test.dart',
]);
expect(test.stdoutStream(), neverEmits(endsWith(' main.<fn>')));
expect(test.stdoutStream(), neverEmits(contains('package:test')));
expect(test.stdoutStream(), neverEmits(contains('dart:async/zone.dart')));
expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
await test.shouldExit(1);
},
);
test('supports node_modules in the package directory', () async {
await d.dir('node_modules', [
d.dir('my_module', [d.file('index.js', 'module.exports.value = 12;')]),
]).create();
await d.file('test.dart', '''
import 'dart:js_interop';
import 'package:test/test.dart';
@JS()
external MyModule require(String name);
@JS()
extension type MyModule(JSObject _) implements JSObject {
external int get value;
}
void main() {
test('can load from a module', () {
expect(require('my_module').value, equals(12));
});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
await test.shouldExit(0);
});
group('with onPlatform', () {
test('respects matching Skips', () async {
await d.file('test.dart', '''
import 'dart:async';
import 'package:test/test.dart';
void main() {
test("fail", () => throw 'oh no', onPlatform: {"node": Skip()});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+0 ~1: All tests skipped.')));
await test.shouldExit(0);
});
test('ignores non-matching Skips', () async {
await d.file('test.dart', '''
import 'dart:async';
import 'package:test/test.dart';
void main() {
test("success", () {}, onPlatform: {"browser": Skip()});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
await test.shouldExit(0);
});
test('matches the current OS', () async {
await d.file('test.dart', '''
import 'dart:async';
import 'package:test/test.dart';
void main() {
test("fail", () => throw 'oh no',
onPlatform: {"${currentOS.identifier}": Skip()});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+0 ~1: All tests skipped.')));
await test.shouldExit(0);
});
test("doesn't match a different OS", () async {
await d.file('test.dart', '''
import 'dart:async';
import 'package:test/test.dart';
void main() {
test("success", () {}, onPlatform: {"$otherOS": Skip()});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
await test.shouldExit(0);
});
});
group('with an @OnPlatform annotation', () {
test('respects matching Skips', () async {
await d.file('test.dart', '''
@OnPlatform(const {"js": const Skip()})
import 'dart:async';
import 'package:test/test.dart';
void main() {
test("fail", () => throw 'oh no');
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('~1: All tests skipped.')));
await test.shouldExit(0);
});
test('ignores non-matching Skips', () async {
await d.file('test.dart', '''
@OnPlatform(const {"vm": const Skip()})
import 'dart:async';
import 'package:test/test.dart';
void main() {
test("success", () {});
}
''').create();
var test = await runTest(['-p', 'node', 'test.dart']);
expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
await test.shouldExit(0);
});
});
}