blob: 4fa1b88a976322da1ffdc49cd97fca82cf0ca81b [file] [log] [blame]
// Copyright (c) 2023, 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:shelf/shelf.dart' as shelf;
import 'package:test_api/backend.dart' show StackTraceMapper, SuitePlatform;
import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
import 'package:web_socket_channel/web_socket_channel.dart'; // ignore: implementation_imports
/// The shared interface for all compiler support libraries.
abstract class CompilerSupport {
/// The global test runner configuration.
final Configuration config;
/// The default template path.
final String defaultTemplatePath;
CompilerSupport(this.config, this.defaultTemplatePath);
/// The URL at which this compiler serves its tests.
///
/// Each compiler serves its tests under a different directory.
Uri get serverUrl;
/// Compiles [dartPath] using [suiteConfig] for [platform].
///
/// [dartPath] is the path to the original `.dart` test suite, relative to the
/// package root.
Future<void> compileSuite(
String dartPath, SuiteConfiguration suiteConfig, SuitePlatform platform);
/// Retrieves a stack trace mapper for [dartPath] if available.
///
/// [dartPath] is the path to the original `.dart` test suite, relative to the
/// package root.
StackTraceMapper? stackTraceMapperForPath(String dartPath);
/// Returns the eventual URI for the web socket, as well as the channel itself
/// once the connection is established.
(Uri uri, Future<WebSocketChannel> socket) get webSocket;
/// Closes down anything necessary for this implementation.
Future<void> close();
/// A handler that serves html wrapper files used to bootstrap tests.
shelf.Response htmlWrapperHandler(shelf.Request request);
}
mixin JsHtmlWrapper on CompilerSupport {
@override
shelf.Response htmlWrapperHandler(shelf.Request request) {
var path = p.fromUri(request.url);
if (path.endsWith('.html')) {
var test = p.setExtension(path, '.dart');
var scriptBase = htmlEscape.convert(p.basename(test));
var link = '<link rel="x-dart-test" href="$scriptBase">';
var testName = htmlEscape.convert(test);
var template = config.customHtmlTemplatePath ?? defaultTemplatePath;
var contents = File(template).readAsStringSync();
var processedContents = contents
// Checked during loading phase that there is only one {{testScript}} placeholder.
.replaceFirst('{{testScript}}', link)
.replaceAll('{{testName}}', testName);
return shelf.Response.ok(processedContents,
headers: {'Content-Type': 'text/html'});
}
return shelf.Response.notFound('Not found.');
}
}
mixin WasmHtmlWrapper on CompilerSupport {
@override
shelf.Response htmlWrapperHandler(shelf.Request request) {
var path = p.fromUri(request.url);
if (path.endsWith('.html')) {
var test = '${p.withoutExtension(path)}.dart';
var scriptBase = htmlEscape.convert(p.basename(test));
var link = '<link rel="x-dart-test" href="$scriptBase">';
var testName = htmlEscape.convert(test);
var template = config.customHtmlTemplatePath ?? defaultTemplatePath;
var contents = File(template).readAsStringSync();
var jsRuntime = p.basename('$test.browser_test.dart.mjs');
var wasmData = '<data id="WasmBootstrapInfo" '
'data-wasmurl="${p.basename('$test.browser_test.dart.wasm')}" '
'data-jsruntimeurl="$jsRuntime"></data>';
var processedContents = contents
// Checked during loading phase that there is only one {{testScript}} placeholder.
.replaceFirst('{{testScript}}', '$link\n$wasmData')
.replaceAll('{{testName}}', testName);
return shelf.Response.ok(processedContents,
headers: {'Content-Type': 'text/html'});
}
return shelf.Response.notFound('Not found.');
}
}