Merge package:test_descriptor into the test monorepo
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..f189cdd
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+.github/workflows/dart.yml linguist-generated=true
+tool/ci.sh linguist-generated=true
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..a53d62f
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+/pkgs/ @dart-lang/core-package-admins
diff --git a/.github/ISSUE_TEMPLATE/01_test_runner_bug.md b/.github/ISSUE_TEMPLATE/01_test_runner_bug.md
new file mode 100644
index 0000000..e49d277
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01_test_runner_bug.md
@@ -0,0 +1,14 @@
+---
+name: I found a bug in the test runner
+about: >-
+ Report a bug in running tests, integration with a specific platform, or
+ any behavior of 'package:test'.
+title: ''
+labels: bug
+---
+Describe the bug in detail.
+Include the specific command you ran and any relevant details about the
+environment, including operating system, Dart SDK version, and the version of
+`package:test` you are using.
+Whenever possible, include a full reproduction example that we can run to
+observe the problem.
diff --git a/.github/ISSUE_TEMPLATE/02_test_runner_feature.md b/.github/ISSUE_TEMPLATE/02_test_runner_feature.md
new file mode 100644
index 0000000..56170ba
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02_test_runner_feature.md
@@ -0,0 +1,10 @@
+---
+name: I'd like a new feature in the test runner
+about: >-
+ Propose a feature for 'package:test' that would make testing easier or more
+ powerful.
+title: ''
+labels: enhancement
+---
+Describe the feature and include specific description of the use case, or use
+cases, you have in mind.
diff --git a/.github/ISSUE_TEMPLATE/03_checks_feedback.md b/.github/ISSUE_TEMPLATE/03_checks_feedback.md
new file mode 100644
index 0000000..5eb0039
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/03_checks_feedback.md
@@ -0,0 +1,8 @@
+---
+name: I have feedback for the package:checks preview
+about: Suggest a change, new feature, or bug, for 'package:checks'.
+title: ''
+labels: package:checks
+---
+Add any feedback about `package:checks`.
+Include a description of a specific use case for any feature requests.
diff --git a/.github/ISSUE_TEMPLATE/fake_async.md b/.github/ISSUE_TEMPLATE/fake_async.md
new file mode 100644
index 0000000..c26edd2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/fake_async.md
@@ -0,0 +1,5 @@
+---
+name: "package:fake_async"
+about: "Create a bug or file a feature request against package:fake_async."
+labels: "package:fake_async"
+---
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/matcher.md b/.github/ISSUE_TEMPLATE/matcher.md
new file mode 100644
index 0000000..f41464d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/matcher.md
@@ -0,0 +1,5 @@
+---
+name: "package:matcher"
+about: "Create a bug or file a feature request against package:matcher."
+labels: "package:matcher"
+---
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..8aa9b97
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,33 @@
+# Dependabot configuration file.
+# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
+
+version: 2
+
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: monthly
+ labels:
+ - autosubmit
+ groups:
+ github-actions:
+ patterns:
+ - "*"
+
+ # Check daily for major version dependency updates for packages which depend
+ # on package:analyzer.
+ - package-ecosystem: "pub"
+ directory: "pkgs/test"
+ schedule:
+ interval: "daily"
+ versioning-strategy: increase-if-necessary
+ reviewers:
+ - "natebosch"
+ - package-ecosystem: "pub"
+ directory: "pkgs/test_core"
+ schedule:
+ interval: "daily"
+ versioning-strategy: increase-if-necessary
+ reviewers:
+ - "natebosch"
diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml
new file mode 100644
index 0000000..9a3cd79
--- /dev/null
+++ b/.github/workflows/dart.yml
@@ -0,0 +1,1903 @@
+# Created with package:mono_repo v6.6.2
+name: Dart CI
+on:
+ push:
+ branches:
+ - main
+ - master
+ pull_request:
+ schedule:
+ - cron: "0 0 * * 0"
+defaults:
+ run:
+ shell: bash
+env:
+ PUB_ENVIRONMENT: bot.github
+permissions: read-all
+
+jobs:
+ job_001:
+ name: mono_repo self validate
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: stable
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - name: mono_repo self validate
+ run: dart pub global activate mono_repo 6.6.2
+ - name: mono_repo self validate
+ run: dart pub global run mono_repo generate --validate
+ job_002:
+ name: "analyze_and_format; linux; Dart 3.3.0; PKG: pkgs/fake_async; `dart analyze`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/fake_async;commands:analyze_1"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/fake_async
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.3.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_fake_async_pub_upgrade
+ name: pkgs/fake_async; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ - name: pkgs/fake_async; dart analyze
+ run: dart analyze
+ if: "always() && steps.pkgs_fake_async_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ job_003:
+ name: "analyze_and_format; linux; Dart 3.4.0; PKG: pkgs/matcher; `dart analyze`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.4.0;packages:pkgs/matcher;commands:analyze_1"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.4.0;packages:pkgs/matcher
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.4.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.4.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_matcher_pub_upgrade
+ name: pkgs/matcher; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ - name: pkgs/matcher; dart analyze
+ run: dart analyze
+ if: "always() && steps.pkgs_matcher_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ job_004:
+ name: "analyze_and_format; linux; Dart 3.5.0; PKGS: integration_tests/regression, integration_tests/wasm; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/regression-integration_tests/wasm;commands:format-analyze_0"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/regression-integration_tests/wasm
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_regression_pub_upgrade
+ name: integration_tests/regression; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - name: "integration_tests/regression; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.integration_tests_regression_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - name: "integration_tests/regression; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.integration_tests_regression_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ job_005:
+ name: "analyze_and_format; linux; Dart 3.5.0; PKGS: pkgs/checks, pkgs/test_core; `dart analyze`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/checks-pkgs/test_core;commands:analyze_1"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/checks-pkgs/test_core
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_checks_pub_upgrade
+ name: pkgs/checks; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/checks
+ - name: pkgs/checks; dart analyze
+ run: dart analyze
+ if: "always() && steps.pkgs_checks_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/checks
+ - id: pkgs_test_core_pub_upgrade
+ name: pkgs/test_core; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ - name: pkgs/test_core; dart analyze
+ run: dart analyze
+ if: "always() && steps.pkgs_test_core_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ job_006:
+ name: "analyze_and_format; linux; Dart dev; PKGS: integration_tests/regression, integration_tests/spawn_hybrid, integration_tests/wasm, pkgs/checks, pkgs/fake_async, pkgs/matcher, pkgs/test, pkgs/test_api, pkgs/test_core; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/regression-integration_tests/spawn_hybrid-integration_tests/wasm-pkgs/checks-pkgs/fake_async-pkgs/matcher-pkgs/test-pkgs/test_api-pkgs/test_core;commands:format-analyze_0"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/regression-integration_tests/spawn_hybrid-integration_tests/wasm-pkgs/checks-pkgs/fake_async-pkgs/matcher-pkgs/test-pkgs/test_api-pkgs/test_core
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_regression_pub_upgrade
+ name: integration_tests/regression; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - name: "integration_tests/regression; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.integration_tests_regression_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - name: "integration_tests/regression; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.integration_tests_regression_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - id: integration_tests_spawn_hybrid_pub_upgrade
+ name: integration_tests/spawn_hybrid; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ - name: "integration_tests/spawn_hybrid; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.integration_tests_spawn_hybrid_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ - name: "integration_tests/spawn_hybrid; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.integration_tests_spawn_hybrid_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - id: pkgs_checks_pub_upgrade
+ name: pkgs/checks; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/checks
+ - name: "pkgs/checks; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.pkgs_checks_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/checks
+ - name: "pkgs/checks; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.pkgs_checks_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/checks
+ - id: pkgs_fake_async_pub_upgrade
+ name: pkgs/fake_async; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ - name: "pkgs/fake_async; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.pkgs_fake_async_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ - name: "pkgs/fake_async; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.pkgs_fake_async_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ - id: pkgs_matcher_pub_upgrade
+ name: pkgs/matcher; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ - name: "pkgs/matcher; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.pkgs_matcher_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ - name: "pkgs/matcher; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.pkgs_matcher_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ - id: pkgs_test_api_pub_upgrade
+ name: pkgs/test_api; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test_api
+ - name: "pkgs/test_api; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.pkgs_test_api_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_api
+ - name: "pkgs/test_api; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.pkgs_test_api_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_api
+ - id: pkgs_test_core_pub_upgrade
+ name: pkgs/test_core; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ - name: "pkgs/test_core; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.pkgs_test_core_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ - name: "pkgs/test_core; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.pkgs_test_core_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ job_007:
+ name: "analyze_and_format; windows; Dart 3.5.0; PKG: integration_tests/wasm; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ job_008:
+ name: "analyze_and_format; windows; Dart dev; PKG: integration_tests/wasm; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart format --output=none --set-exit-if-changed ."
+ run: "dart format --output=none --set-exit-if-changed ."
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart analyze --fatal-infos"
+ run: dart analyze --fatal-infos
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ job_009:
+ name: "unit_test; linux; Dart 3.3.0; PKG: pkgs/fake_async; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/fake_async;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/fake_async
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.3.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_fake_async_pub_upgrade
+ name: pkgs/fake_async; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ - name: pkgs/fake_async; dart test
+ run: dart test
+ if: "always() && steps.pkgs_fake_async_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_010:
+ name: "unit_test; linux; Dart 3.4.0; PKG: pkgs/matcher; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.4.0;packages:pkgs/matcher;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.4.0;packages:pkgs/matcher
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.4.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.4.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_matcher_pub_upgrade
+ name: pkgs/matcher; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ - name: pkgs/matcher; dart test
+ run: dart test
+ if: "always() && steps.pkgs_matcher_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_011:
+ name: "unit_test; linux; Dart 3.5.0; PKG: integration_tests/regression; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/regression;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/regression
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_regression_pub_upgrade
+ name: integration_tests/regression; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - name: integration_tests/regression; dart test
+ run: dart test
+ if: "always() && steps.integration_tests_regression_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_012:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/checks; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/checks;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/checks
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_checks_pub_upgrade
+ name: pkgs/checks; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/checks
+ - name: pkgs/checks; dart test
+ run: dart test
+ if: "always() && steps.pkgs_checks_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/checks
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_013:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/test_core; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test_core;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test_core
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_core_pub_upgrade
+ name: pkgs/test_core; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ - name: pkgs/test_core; dart test
+ run: dart test
+ if: "always() && steps.pkgs_test_core_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_014:
+ name: "unit_test; linux; Dart 3.5.0; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/spawn_hybrid;commands:test_1"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/spawn_hybrid
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_spawn_hybrid_pub_upgrade
+ name: integration_tests/spawn_hybrid; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ - name: "integration_tests/spawn_hybrid; dart test -p chrome,vm,node"
+ run: "dart test -p chrome,vm,node"
+ if: "always() && steps.integration_tests_spawn_hybrid_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_015:
+ name: "unit_test; linux; Dart 3.5.0; PKG: integration_tests/wasm; `dart test --timeout=60s`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/wasm;commands:test_2"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:integration_tests/wasm
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart test --timeout=60s"
+ run: "dart test --timeout=60s"
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_016:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 0`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_01"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 0"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 0"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_017:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 1`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_02"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 1"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 1"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_018:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 2`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_03"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 2"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 2"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_019:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 3`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_04"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 3"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 3"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_020:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 4`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_05"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 4"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 4"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_021:
+ name: "unit_test; linux; Dart 3.5.0; PKG: pkgs/test_api; `dart test --preset travis -x browser`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test_api;commands:command_11"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test_api
+ os:ubuntu-latest;pub-cache-hosted;sdk:3.5.0
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_api_pub_upgrade
+ name: pkgs/test_api; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test_api
+ - name: "pkgs/test_api; dart test --preset travis -x browser"
+ run: dart test --preset travis -x browser
+ if: "always() && steps.pkgs_test_api_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_api
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_022:
+ name: "unit_test; linux; Dart dev; PKG: integration_tests/regression; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/regression;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/regression
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_regression_pub_upgrade
+ name: integration_tests/regression; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ - name: integration_tests/regression; dart test
+ run: dart test
+ if: "always() && steps.integration_tests_regression_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/regression
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_023:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/checks; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/checks;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/checks
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_checks_pub_upgrade
+ name: pkgs/checks; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/checks
+ - name: pkgs/checks; dart test
+ run: dart test
+ if: "always() && steps.pkgs_checks_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/checks
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_024:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/fake_async; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/fake_async;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/fake_async
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_fake_async_pub_upgrade
+ name: pkgs/fake_async; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ - name: pkgs/fake_async; dart test
+ run: dart test
+ if: "always() && steps.pkgs_fake_async_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/fake_async
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_025:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/matcher; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/matcher;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/matcher
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_matcher_pub_upgrade
+ name: pkgs/matcher; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ - name: pkgs/matcher; dart test
+ run: dart test
+ if: "always() && steps.pkgs_matcher_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/matcher
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_026:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/test_core; `dart test`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test_core;commands:command_00"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test_core
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_core_pub_upgrade
+ name: pkgs/test_core; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ - name: pkgs/test_core; dart test
+ run: dart test
+ if: "always() && steps.pkgs_test_core_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_core
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_027:
+ name: "unit_test; linux; Dart dev; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/spawn_hybrid;commands:test_1"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/spawn_hybrid
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_spawn_hybrid_pub_upgrade
+ name: integration_tests/spawn_hybrid; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ - name: "integration_tests/spawn_hybrid; dart test -p chrome,vm,node"
+ run: "dart test -p chrome,vm,node"
+ if: "always() && steps.integration_tests_spawn_hybrid_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_028:
+ name: "unit_test; linux; Dart dev; PKG: integration_tests/wasm; `dart test --timeout=60s`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/wasm;commands:test_2"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:integration_tests/wasm
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart test --timeout=60s"
+ run: "dart test --timeout=60s"
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_029:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 0`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test;commands:command_01"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 0"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 0"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_030:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 1`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test;commands:command_02"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 1"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 1"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_031:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 2`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test;commands:command_03"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 2"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 2"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_032:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 3`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test;commands:command_04"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 3"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 3"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_033:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 4`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test;commands:command_05"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 4"
+ run: "xvfb-run -s \"-screen 0 1024x768x24\" dart test --preset travis --total-shards 5 --shard-index 4"
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_034:
+ name: "unit_test; linux; Dart dev; PKG: pkgs/test_api; `dart test --preset travis -x browser`"
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test_api;commands:command_11"
+ restore-keys: |
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/test_api
+ os:ubuntu-latest;pub-cache-hosted;sdk:dev
+ os:ubuntu-latest;pub-cache-hosted
+ os:ubuntu-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_api_pub_upgrade
+ name: pkgs/test_api; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test_api
+ - name: "pkgs/test_api; dart test --preset travis -x browser"
+ run: dart test --preset travis -x browser
+ if: "always() && steps.pkgs_test_api_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test_api
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_035:
+ name: "unit_test; osx; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 0`"
+ runs-on: macos-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_06"
+ restore-keys: |
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0
+ os:macos-latest;pub-cache-hosted
+ os:macos-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 0"
+ run: dart test --preset travis --total-shards 5 --shard-index 0
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_036:
+ name: "unit_test; osx; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 1`"
+ runs-on: macos-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_07"
+ restore-keys: |
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0
+ os:macos-latest;pub-cache-hosted
+ os:macos-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 1"
+ run: dart test --preset travis --total-shards 5 --shard-index 1
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_037:
+ name: "unit_test; osx; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 2`"
+ runs-on: macos-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_08"
+ restore-keys: |
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0
+ os:macos-latest;pub-cache-hosted
+ os:macos-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 2"
+ run: dart test --preset travis --total-shards 5 --shard-index 2
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_038:
+ name: "unit_test; osx; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 3`"
+ runs-on: macos-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_09"
+ restore-keys: |
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0
+ os:macos-latest;pub-cache-hosted
+ os:macos-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 3"
+ run: dart test --preset travis --total-shards 5 --shard-index 3
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_039:
+ name: "unit_test; osx; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 4`"
+ runs-on: macos-latest
+ steps:
+ - name: Cache Pub hosted dependencies
+ uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a
+ with:
+ path: "~/.pub-cache/hosted"
+ key: "os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test;commands:command_10"
+ restore-keys: |
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0;packages:pkgs/test
+ os:macos-latest;pub-cache-hosted;sdk:3.5.0
+ os:macos-latest;pub-cache-hosted
+ os:macos-latest
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 4"
+ run: dart test --preset travis --total-shards 5 --shard-index 4
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_040:
+ name: "unit_test; windows; Dart 3.5.0; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_spawn_hybrid_pub_upgrade
+ name: integration_tests/spawn_hybrid; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ - name: "integration_tests/spawn_hybrid; dart test -p chrome,vm,node"
+ run: "dart test -p chrome,vm,node"
+ if: "always() && steps.integration_tests_spawn_hybrid_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_041:
+ name: "unit_test; windows; Dart 3.5.0; PKG: integration_tests/wasm; `dart test --timeout=60s`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart test --timeout=60s"
+ run: "dart test --timeout=60s"
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_042:
+ name: "unit_test; windows; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 0`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 0"
+ run: dart test --preset travis --total-shards 5 --shard-index 0
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_043:
+ name: "unit_test; windows; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 1`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 1"
+ run: dart test --preset travis --total-shards 5 --shard-index 1
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_044:
+ name: "unit_test; windows; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 2`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 2"
+ run: dart test --preset travis --total-shards 5 --shard-index 2
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_045:
+ name: "unit_test; windows; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 3`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 3"
+ run: dart test --preset travis --total-shards 5 --shard-index 3
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_046:
+ name: "unit_test; windows; Dart 3.5.0; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 4`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: "3.5.0"
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: pkgs_test_pub_upgrade
+ name: pkgs/test; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: pkgs/test
+ - name: "pkgs/test; dart test --preset travis --total-shards 5 --shard-index 4"
+ run: dart test --preset travis --total-shards 5 --shard-index 4
+ if: "always() && steps.pkgs_test_pub_upgrade.conclusion == 'success'"
+ working-directory: pkgs/test
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_047:
+ name: "unit_test; windows; Dart dev; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_spawn_hybrid_pub_upgrade
+ name: integration_tests/spawn_hybrid; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ - name: "integration_tests/spawn_hybrid; dart test -p chrome,vm,node"
+ run: "dart test -p chrome,vm,node"
+ if: "always() && steps.integration_tests_spawn_hybrid_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/spawn_hybrid
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_048:
+ name: "unit_test; windows; Dart dev; PKG: integration_tests/wasm; `dart test --timeout=60s`"
+ runs-on: windows-latest
+ steps:
+ - name: Setup Dart SDK
+ uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94
+ with:
+ sdk: dev
+ - id: checkout
+ name: Checkout repository
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - id: integration_tests_wasm_pub_upgrade
+ name: integration_tests/wasm; dart pub upgrade
+ run: dart pub upgrade
+ if: "always() && steps.checkout.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ - name: "integration_tests/wasm; dart test --timeout=60s"
+ run: "dart test --timeout=60s"
+ if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'"
+ working-directory: integration_tests/wasm
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ job_049:
+ name: Notify failure
+ runs-on: ubuntu-latest
+ if: "(github.event_name == 'push' || github.event_name == 'schedule') && failure()"
+ steps:
+ - run: |
+ curl -H "Content-Type: application/json" -X POST -d \
+ "{'text':'Build failed! ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}'}" \
+ "${CHAT_WEBHOOK_URL}"
+ env:
+ CHAT_WEBHOOK_URL: "${{ secrets.BUILD_AND_TEST_TEAM_CHAT_WEBHOOK_URL }}"
+ needs:
+ - job_001
+ - job_002
+ - job_003
+ - job_004
+ - job_005
+ - job_006
+ - job_007
+ - job_008
+ - job_009
+ - job_010
+ - job_011
+ - job_012
+ - job_013
+ - job_014
+ - job_015
+ - job_016
+ - job_017
+ - job_018
+ - job_019
+ - job_020
+ - job_021
+ - job_022
+ - job_023
+ - job_024
+ - job_025
+ - job_026
+ - job_027
+ - job_028
+ - job_029
+ - job_030
+ - job_031
+ - job_032
+ - job_033
+ - job_034
+ - job_035
+ - job_036
+ - job_037
+ - job_038
+ - job_039
+ - job_040
+ - job_041
+ - job_042
+ - job_043
+ - job_044
+ - job_045
+ - job_046
+ - job_047
+ - job_048
diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml
new file mode 100644
index 0000000..1656c16
--- /dev/null
+++ b/.github/workflows/health.yaml
@@ -0,0 +1,12 @@
+name: Health
+on:
+ pull_request:
+ branches: [ main, master ]
+ types: [opened, synchronize, reopened, labeled, unlabeled]
+jobs:
+ health:
+ uses: dart-lang/ecosystem/.github/workflows/health.yaml@main
+ with:
+ checks: "version,changelog,do-not-submit"
+ permissions:
+ pull-requests: write
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
new file mode 100644
index 0000000..1a6f2ec
--- /dev/null
+++ b/.github/workflows/no-response.yml
@@ -0,0 +1,35 @@
+# A workflow to close issues where the author hasn't responded to a request for
+# more information; see https://github.com/actions/stale.
+
+name: No Response
+
+# Run as a daily cron.
+on:
+ schedule:
+ # Every day at 8am
+ - cron: '0 8 * * *'
+
+# All permissions not specified are set to 'none'.
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ no-response:
+ runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'dart-lang' }}
+ steps:
+ - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e
+ with:
+ days-before-stale: -1
+ days-before-close: 14
+ stale-issue-label: "needs-info"
+ close-issue-message: >
+ Without additional information we're not able to resolve this issue.
+ Feel free to add more info or respond to any questions above and we
+ can reopen the case. Thanks for your contribution!
+ stale-pr-label: "needs-info"
+ close-pr-message: >
+ Without additional information we're not able to resolve this PR.
+ Feel free to add more info or respond to any questions above.
+ Thanks for your contribution!
diff --git a/.github/workflows/post_summaries.yaml b/.github/workflows/post_summaries.yaml
new file mode 100644
index 0000000..9be7d5d
--- /dev/null
+++ b/.github/workflows/post_summaries.yaml
@@ -0,0 +1,18 @@
+name: Comment on the pull request
+
+on:
+ # Trigger this workflow after the Health workflow completes. This workflow
+ # will have permissions to do things like create comments on the PR, even if
+ # the original workflow couldn't.
+ workflow_run:
+ workflows:
+ - Health
+ - Publish
+ types:
+ - completed
+
+jobs:
+ upload:
+ uses: dart-lang/ecosystem/.github/workflows/post_summaries.yaml@main
+ permissions:
+ pull-requests: write
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..7d6442c
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,17 @@
+# A CI configuration to auto-publish pub packages.
+
+name: Publish
+
+on:
+ pull_request:
+ branches: [ master ]
+ types: [opened, synchronize, reopened, labeled, unlabeled]
+ push:
+ tags: [ '[A-z]+-v[0-9]+.[0-9]+.[0-9]+*' ]
+
+jobs:
+ publish:
+ if: ${{ github.repository_owner == 'dart-lang' }}
+ uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main
+ with:
+ write-comments: false
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
new file mode 100644
index 0000000..3c5c10a
--- /dev/null
+++ b/.github/workflows/scorecards-analysis.yml
@@ -0,0 +1,55 @@
+name: Scorecards supply-chain security
+on:
+ # Only the default branch is supported.
+ branch_protection_rule:
+ push:
+ branches: [ master ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecards analysis
+ runs-on: ubuntu-latest
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ actions: read
+ contents: read
+ # Needed to access OIDC token.
+ id-token: write
+
+ steps:
+ - name: "Checkout code"
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # Read-only PAT token. To create it,
+ # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
+ repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
+ # Publish the results to enable scorecard badges. For more details, see
+ # https://github.com/ossf/scorecard-action#publishing-results.
+ # For private repositories, `publish_results` will automatically be set to `false`,
+ # regardless of the value entered here.
+ publish_results: true
+
+ # Upload the results as artifacts (optional).
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ # Upload the results to GitHub's code scanning dashboard.
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
+ with:
+ sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..56c3800
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.dart_tool/
+.pub/
+build/
+packages
+
+# Include when developing application packages.
+pubspec.lock
+.packages
+
+# Ignore common coverage directory
+coverage/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..90003c0
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,74 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement][CLA] (CLA), which you can do
+online. The CLA is necessary mainly because you own the copyright to your
+changes, even after your contribution becomes part of our codebase, so we need
+your permission to use and distribute your code. We also need to be sure of
+various other things—for instance that you'll tell us if you know that your code
+infringes on other people's patents. You don't have to sign the CLA until after
+you've submitted your code for review and a member has approved it, but you must
+do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+[CLA]: https://cla.developers.google.com/about/google-individual
+
+### Code reviews
+
+All submissions, including submissions by project members, require review. We
+recommend [forking the repository][fork], making changes in your fork, and
+[sending us a pull request][pr] so we can review the changes and merge them into
+this repository.
+
+[fork]: https://help.github.com/articles/about-forks/
+[pr]: https://help.github.com/articles/creating-a-pull-request/
+
+Functional changes will require tests to be added or changed. The tests live in
+the `test/` directory for each package, and are run with `dart test`. If you
+need to create new tests, use the existing tests as a guideline for what they
+should look like.
+
+You can run all additional presubmit checks locally if you wish, by using the
+`mono_repo` tool. From the root of the repository, run
+`pub global run mono_repo presubmit`.
+
+### Versioning
+
+You will also need to potentially update the pubspec.yaml and/or CHANGELOG.md of
+any package you are updating. If the current version is not a `-dev` version
+then you should update it and add a new header to the changelog. If you have no
+publicly facing change to list, it is OK for there to be no changes listed.
+
+We follow pretty strict semantic versioning, feel free to ask on the PR if you
+are unsure about what version number you should choose (or do your best, and a
+code reviewers will bring it up if it is incorrect).
+
+### File headers
+
+All files in the project must start with the following header.
+
+ // 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.
+
+### Publishing
+
+Publishing is done by package owners creating a tag of the form
+`<package>-v<version>`, either manually or through the github release ui
+(preferred). The pubspec and changelog must already be updated to the desired
+release version in the master branch for this process to work.
+
+### The small print
+
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement][CCLA].
+
+[CCLA]: https://developers.google.com/open-source/cla/corporate
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..25b50aa
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+[](https://github.com/dart-lang/test/actions/workflows/dart.yml)
+[](https://deps.dev/project/github/dart-lang%2Ftest)
+
+## What's here?
+
+Welcome! [package:test](pkgs/test/) is the standard testing library for Dart and
+Flutter. If you have questions about Dart testing, please see the docs for
+[package:test](pkgs/test/). `package:test_api` and `package:test_core`
+are implementation details and generally not user-facing.
+
+[package:checks](pkgs/checks/) is a relatively new library for expressing test
+expectations. It's a more modern version of `package:matcher` and features a
+literate API.
+
+## Packages
+
+| Package | Description | Version |
+|---|---|---|
+| [checks](pkgs/checks/) | A framework for checking values against expectations and building custom expectations. | [](https://pub.dev/packages/checks) |
+| [fake_async](pkgs/fake_async/) | Fake asynchronous events such as timers and microtasks for deterministic testing. | [](https://pub.dev/packages/fake_async) |
+| [matcher](pkgs/matcher/) | Support for specifying test expectations via an extensible Matcher class. | [](https://pub.dev/packages/matcher) |
+| [test](pkgs/test/) | A full featured library for writing and running Dart tests across platforms. | [](https://pub.dev/packages/test) |
+| [test_api](pkgs/test_api/) | | [](https://pub.dev/packages/test_api) |
+| [test_core](pkgs/test_core/) | | [](https://pub.dev/packages/test_core) |
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..f6b2dc1
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,22 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+
+
+analyzer:
+ language:
+ strict-casts: true
+ errors:
+ # There are a number of deprecated members used through this package
+ deprecated_member_use_from_same_package: ignore
+
+ # Ignoring a number of lints from dart_flutter_team_lints – for now
+ avoid_catching_errors: ignore
+ avoid_dynamic_calls: ignore
+ comment_references: ignore
+ lines_longer_than_80_chars: ignore
+ only_throw_errors: ignore
+ unawaited_futures: ignore
+ unsafe_html: ignore
+
+linter:
+ rules:
+ - avoid_private_typedef_functions
diff --git a/integration_tests/regression/dart_test.yaml b/integration_tests/regression/dart_test.yaml
new file mode 100644
index 0000000..07fd2f5
--- /dev/null
+++ b/integration_tests/regression/dart_test.yaml
@@ -0,0 +1,13 @@
+paths:
+- lib/issue_2142/test.dart
+
+platforms:
+- chrome
+- vm
+
+compilers:
+- dart2js
+- dart2wasm
+- exe
+- kernel
+- source
diff --git a/integration_tests/regression/lib/issue_2142/import.dart b/integration_tests/regression/lib/issue_2142/import.dart
new file mode 100644
index 0000000..987fbb1
--- /dev/null
+++ b/integration_tests/regression/lib/issue_2142/import.dart
@@ -0,0 +1,8 @@
+// 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.
+
+// ignore: prefer_relative_imports
+import 'package:regression_tests/issue_2142/test.dart';
+
+Thing newThing() => Thing();
diff --git a/integration_tests/regression/lib/issue_2142/test.dart b/integration_tests/regression/lib/issue_2142/test.dart
new file mode 100644
index 0000000..06afc5a
--- /dev/null
+++ b/integration_tests/regression/lib/issue_2142/test.dart
@@ -0,0 +1,14 @@
+// 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 'package:test/test.dart';
+import 'import.dart';
+
+void main() {
+ test('aThing is a Thing', () {
+ expect(newThing(), isA<Thing>());
+ });
+}
+
+class Thing {}
diff --git a/integration_tests/regression/mono_pkg.yaml b/integration_tests/regression/mono_pkg.yaml
new file mode 100644
index 0000000..4a60bc1
--- /dev/null
+++ b/integration_tests/regression/mono_pkg.yaml
@@ -0,0 +1,16 @@
+# See https://pub.dev/packages/mono_repo
+
+sdk:
+- dev
+- pubspec
+
+os:
+- linux
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+- unit_test:
+ - test
diff --git a/integration_tests/regression/pubspec.yaml b/integration_tests/regression/pubspec.yaml
new file mode 100644
index 0000000..6987181
--- /dev/null
+++ b/integration_tests/regression/pubspec.yaml
@@ -0,0 +1,7 @@
+name: regression_tests
+publish_to: none
+environment:
+ sdk: ^3.5.0
+resolution: workspace
+dependencies:
+ test: any
diff --git a/integration_tests/spawn_hybrid/lib/emits_numbers.dart b/integration_tests/spawn_hybrid/lib/emits_numbers.dart
new file mode 100644
index 0000000..6429a85
--- /dev/null
+++ b/integration_tests/spawn_hybrid/lib/emits_numbers.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, 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 'package:stream_channel/stream_channel.dart';
+
+void hybridMain(StreamChannel channel) {
+ channel.sink
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+}
diff --git a/integration_tests/spawn_hybrid/mono_pkg.yaml b/integration_tests/spawn_hybrid/mono_pkg.yaml
new file mode 100644
index 0000000..7d75b61
--- /dev/null
+++ b/integration_tests/spawn_hybrid/mono_pkg.yaml
@@ -0,0 +1,18 @@
+# See https://pub.dev/packages/mono_repo
+
+sdk:
+- dev
+- pubspec
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+ sdk:
+ - dev
+- unit_test:
+ - test: -p chrome,vm,node
+ os:
+ - linux
+ - windows
diff --git a/integration_tests/spawn_hybrid/other_package/lib/emits_numbers.dart b/integration_tests/spawn_hybrid/other_package/lib/emits_numbers.dart
new file mode 100644
index 0000000..6429a85
--- /dev/null
+++ b/integration_tests/spawn_hybrid/other_package/lib/emits_numbers.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, 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 'package:stream_channel/stream_channel.dart';
+
+void hybridMain(StreamChannel channel) {
+ channel.sink
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+}
diff --git a/integration_tests/spawn_hybrid/other_package/pubspec.yaml b/integration_tests/spawn_hybrid/other_package/pubspec.yaml
new file mode 100644
index 0000000..b7d026f
--- /dev/null
+++ b/integration_tests/spawn_hybrid/other_package/pubspec.yaml
@@ -0,0 +1,6 @@
+name: other_package
+publish_to: none
+environment:
+ sdk: '>=2.12.0 <3.0.0'
+dependencies:
+ stream_channel: ^2.1.0
diff --git a/integration_tests/spawn_hybrid/pubspec.yaml b/integration_tests/spawn_hybrid/pubspec.yaml
new file mode 100644
index 0000000..de9e5e9
--- /dev/null
+++ b/integration_tests/spawn_hybrid/pubspec.yaml
@@ -0,0 +1,13 @@
+name: spawn_hybrid
+publish_to: none
+environment:
+ sdk: ^3.5.0
+resolution: workspace
+dependencies:
+ async: ^2.9.0
+ path: ^1.8.2
+ stream_channel: ^2.1.0
+dev_dependencies:
+ other_package:
+ path: other_package/
+ test: any
diff --git a/integration_tests/spawn_hybrid/test/hybrid_test.dart b/integration_tests/spawn_hybrid/test/hybrid_test.dart
new file mode 100644
index 0000000..1493e23
--- /dev/null
+++ b/integration_tests/spawn_hybrid/test/hybrid_test.dart
@@ -0,0 +1,357 @@
+// Copyright (c) 2022, 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 'package:async/async.dart';
+import 'package:path/path.dart' as p;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('spawnHybridUri():', () {
+ test('loads a file in a separate isolate connected via StreamChannel',
+ () async {
+ expect(spawnHybridUri('util/emits_numbers.dart').stream.toList(),
+ completion(equals([1, 2, 3])));
+ });
+
+ test('resolves root-relative URIs relative to the package root', () async {
+ expect(spawnHybridUri('/test/util/emits_numbers.dart').stream.toList(),
+ completion(equals([1, 2, 3])));
+ });
+
+ test('supports Uri objects', () async {
+ expect(
+ spawnHybridUri(Uri.parse('util/emits_numbers.dart')).stream.toList(),
+ completion(equals([1, 2, 3])));
+ });
+
+ test('supports package: uris referencing the root package', () async {
+ expect(
+ spawnHybridUri(Uri.parse('package:spawn_hybrid/emits_numbers.dart'))
+ .stream
+ .toList(),
+ completion(equals([1, 2, 3])));
+ });
+
+ test('supports package: uris referencing dependency packages', () async {
+ expect(
+ spawnHybridUri(Uri.parse('package:other_package/emits_numbers.dart'))
+ .stream
+ .toList(),
+ completion(equals([1, 2, 3])));
+ });
+
+ test('rejects non-String, non-Uri objects', () {
+ expect(() => spawnHybridUri(123), throwsArgumentError);
+ });
+
+ test('passes a message to the hybrid isolate', () async {
+ expect(
+ spawnHybridUri('util/echos_message.dart', message: 123).stream.first,
+ completion(equals(123)));
+ expect(
+ spawnHybridUri('util/echos_message.dart', message: 'wow')
+ .stream
+ .first,
+ completion(equals('wow')));
+ });
+
+ test('emits an error from the stream channel if the isolate fails to load',
+ () {
+ expect(spawnHybridUri('non existent file').stream.first,
+ throwsA(isA<Exception>()));
+ });
+ });
+
+ group('spawnHybridCode()', () {
+ test('loads the code in a separate isolate connected via StreamChannel',
+ () {
+ expect(spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ channel.sink..add(1)..add(2)..add(3)..close();
+ }
+ ''').stream.toList(), completion(equals([1, 2, 3])));
+ });
+
+ test('allows a first parameter with type StreamChannel<Object?>', () {
+ expect(spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel<Object?> channel) {
+ channel.sink..add(1)..add(2)..add(null)..close();
+ }
+ ''').stream.toList(), completion(equals([1, 2, null])));
+ });
+
+ test('gives a good error when the StreamChannel type is not supported', () {
+ expect(
+ spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel<Object> channel) {
+ channel.sink..add(1)..add(2)..add(3)..close();
+ }
+ ''').stream,
+ emitsError(isA<Exception>().having(
+ (e) => e.toString(),
+ 'toString',
+ contains(
+ 'The first parameter to the top-level hybridMain() must be a '
+ 'StreamChannel<dynamic> or StreamChannel<Object?>. More specific '
+ 'types such as StreamChannel<Object> are not supported.'))));
+ });
+
+ test('can use dart:io even when run from a browser', () async {
+ var path = p.join('test', 'hybrid_test.dart');
+ expect(spawnHybridCode("""
+ import 'dart:io';
+
+ import 'package:stream_channel/stream_channel.dart';
+
+ void hybridMain(StreamChannel channel) {
+ channel.sink
+ ..add(File(r"$path").readAsStringSync())
+ ..close();
+ }
+ """).stream.first, completion(contains('hybrid emits numbers')));
+ }, testOn: 'browser');
+
+ test('forwards data from the test to the hybrid isolate', () async {
+ var channel = spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ channel.stream.listen((num) {
+ channel.sink.add(num + 1);
+ });
+ }
+ ''');
+ channel.sink
+ ..add(1)
+ ..add(2)
+ ..add(3);
+ expect(channel.stream.take(3).toList(), completion(equals([2, 3, 4])));
+ });
+
+ test('passes an initial message to the hybrid isolate', () {
+ var code = '''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel, Object message) {
+ channel.sink..add(message)..close();
+ }
+ ''';
+
+ expect(spawnHybridCode(code, message: [1, 2, 3]).stream.first,
+ completion(equals([1, 2, 3])));
+ expect(spawnHybridCode(code, message: {'a': 'b'}).stream.first,
+ completion(equals({'a': 'b'})));
+ });
+
+ test('allows the hybrid isolate to send errors across the stream channel',
+ () {
+ var channel = spawnHybridCode('''
+ import "package:stack_trace/stack_trace.dart";
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ channel.sink.addError("oh no!", Trace.current());
+ }
+ ''');
+
+ channel.stream.listen(null, onError: expectAsync2((error, stackTrace) {
+ expect(error.toString(), equals('oh no!'));
+ expect(stackTrace.toString(), contains('hybridMain'));
+ }));
+ });
+
+ test('sends an unhandled synchronous error across the stream channel', () {
+ var channel = spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ throw "oh no!";
+ }
+ ''');
+
+ channel.stream.listen(null, onError: expectAsync2((error, stackTrace) {
+ expect(error.toString(), equals('oh no!'));
+ expect(stackTrace.toString(), contains('hybridMain'));
+ }));
+ });
+
+ test('sends an unhandled asynchronous error across the stream channel', () {
+ var channel = spawnHybridCode('''
+ import 'dart:async';
+
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ scheduleMicrotask(() {
+ throw "oh no!";
+ });
+ }
+ ''');
+
+ channel.stream.listen(null, onError: expectAsync2((error, stackTrace) {
+ expect(error.toString(), equals('oh no!'));
+ expect(stackTrace.toString(), contains('hybridMain'));
+ }));
+ });
+
+ test('deserializes TestFailures as TestFailures', () {
+ var channel = spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ import "package:test/test.dart";
+
+ void hybridMain(StreamChannel channel) {
+ throw TestFailure("oh no!");
+ }
+ ''');
+
+ expect(channel.stream.first, throwsA(isA<TestFailure>()));
+ });
+
+ test('gracefully handles an unserializable message in the VM', () {
+ var channel = spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {}
+ ''');
+
+ expect(() => channel.sink.add(<Object>[].iterator), throwsArgumentError);
+ });
+
+ test('gracefully handles an unserializable message in the browser',
+ () async {
+ var channel = spawnHybridCode('''
+ import 'package:stream_channel/stream_channel.dart';
+
+ void hybridMain(StreamChannel channel) {}
+ ''');
+
+ expect(() => channel.sink.add(<Object>[].iterator), throwsArgumentError);
+ }, testOn: 'browser');
+
+ test('gracefully handles an unserializable message in the hybrid isolate',
+ () {
+ var channel = spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ channel.sink.add([].iterator);
+ }
+ ''');
+
+ channel.stream.listen(null, onError: expectAsync1((error) {
+ expect(error.toString(), contains("can't be JSON-encoded."));
+ }));
+ });
+
+ test('forwards prints from the hybrid isolate', () {
+ expect(() async {
+ var channel = spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ print("hi!");
+ channel.sink.add(null);
+ }
+ ''');
+ await channel.stream.first;
+ }, prints('hi!\n'));
+ });
+
+ // This takes special handling, since the code is packed into a data: URI
+ // that's imported, URIs don't escape $ by default, and $ isn't allowed in
+ // imports.
+ test('supports a dollar character in the hybrid code', () {
+ expect(spawnHybridCode(r'''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ var value = "bar";
+ channel.sink.add("foo${value}baz");
+ }
+ ''').stream.first, completion('foobarbaz'));
+ });
+
+ test('closes the channel when the hybrid isolate exits', () {
+ var channel = spawnHybridCode('''
+ import "dart:isolate";
+
+ hybridMain(_) {
+ Isolate.current.kill();
+ }
+ ''');
+
+ expect(channel.stream.toList(), completion(isEmpty));
+ });
+
+ group('closes the channel when the test finishes by default', () {
+ late StreamChannel channel;
+
+ test('test 1', () {
+ channel = spawnHybridCode('''
+ import 'package:stream_channel/stream_channel.dart';
+
+ void hybridMain(StreamChannel channel) {}
+ ''');
+ });
+
+ test('test 2', () async {
+ var isDone = false;
+ channel.stream.listen(null, onDone: () => isDone = true);
+ await pumpEventQueue();
+ expect(isDone, isTrue);
+ });
+ });
+
+ group('persists across multiple tests with stayAlive: true', () {
+ late StreamQueue queue;
+ late StreamSink sink;
+ setUpAll(() {
+ var channel = spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ void hybridMain(StreamChannel channel) {
+ channel.stream.listen((message) {
+ channel.sink.add(message);
+ });
+ }
+ ''', stayAlive: true);
+ queue = StreamQueue(channel.stream);
+ sink = channel.sink;
+ });
+
+ test('echoes a number', () {
+ expect(queue.next, completion(equals(123)));
+ sink.add(123);
+ });
+
+ test('echoes a string', () {
+ expect(queue.next, completion(equals('wow')));
+ sink.add('wow');
+ });
+ });
+
+ test('opts in to null safety by default', () async {
+ expect(spawnHybridCode('''
+ import "package:stream_channel/stream_channel.dart";
+
+ // Use some null safety syntax
+ int? x;
+
+ void hybridMain(StreamChannel channel) {
+ channel.sink..add(1)..add(2)..add(3)..close();
+ }
+ ''').stream.toList(), completion(equals([1, 2, 3])));
+ });
+ });
+}
diff --git a/integration_tests/spawn_hybrid/test/hybrid_test_io.dart b/integration_tests/spawn_hybrid/test/hybrid_test_io.dart
new file mode 100644
index 0000000..0e454db
--- /dev/null
+++ b/integration_tests/spawn_hybrid/test/hybrid_test_io.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2022, 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')
+library;
+
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ test('kills the isolate when the test closes the channel', () async {
+ var channel = spawnHybridCode('''
+ import "dart:async";
+ import "dart:io";
+
+ import "package:shelf/shelf.dart" as shelf;
+ import "package:shelf/shelf_io.dart" as io;
+ import "package:stream_channel/stream_channel.dart";
+
+ hybridMain(StreamChannel channel) async {
+ var server = await ServerSocket.bind("localhost", 0);
+ server.listen(null);
+ channel.sink.add(server.port);
+ }
+ ''');
+
+ // Expect that the socket disconnects at some point (presumably when the
+ // isolate closes).
+ var port = await channel.stream.first as int;
+ var socket = await Socket.connect('localhost', port);
+ expect(socket.listen(null).asFuture<void>(), completes);
+
+ await channel.sink.close();
+ });
+
+ test('spawnHybridUri(): supports absolute file: URIs', () async {
+ expect(
+ spawnHybridUri(p.toUri(p.absolute(
+ p.relative(p.join('test', 'util', 'emits_numbers.dart')))))
+ .stream
+ .toList(),
+ completion(equals([1, 2, 3])));
+ });
+}
diff --git a/integration_tests/spawn_hybrid/test/subdir/hybrid_test.dart b/integration_tests/spawn_hybrid/test/subdir/hybrid_test.dart
new file mode 100644
index 0000000..088375c
--- /dev/null
+++ b/integration_tests/spawn_hybrid/test/subdir/hybrid_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2022, 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')
+library;
+
+import 'package:test/test.dart';
+
+void main() {
+ group('spawnHybridUri():', () {
+ test('loads uris relative to the test file', () async {
+ expect(
+ spawnHybridUri(Uri.parse('../util/emits_numbers.dart'))
+ .stream
+ .toList(),
+ completion(equals([1, 2, 3])));
+ });
+ });
+}
diff --git a/integration_tests/spawn_hybrid/test/util/echos_message.dart b/integration_tests/spawn_hybrid/test/util/echos_message.dart
new file mode 100644
index 0000000..271b44f
--- /dev/null
+++ b/integration_tests/spawn_hybrid/test/util/echos_message.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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 'package:stream_channel/stream_channel.dart';
+
+void hybridMain(StreamChannel channel, Object message) {
+ channel.sink
+ ..add(message)
+ ..close();
+}
diff --git a/integration_tests/spawn_hybrid/test/util/emits_numbers.dart b/integration_tests/spawn_hybrid/test/util/emits_numbers.dart
new file mode 100644
index 0000000..6429a85
--- /dev/null
+++ b/integration_tests/spawn_hybrid/test/util/emits_numbers.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, 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 'package:stream_channel/stream_channel.dart';
+
+void hybridMain(StreamChannel channel) {
+ channel.sink
+ ..add(1)
+ ..add(2)
+ ..add(3)
+ ..close();
+}
diff --git a/integration_tests/wasm/dart_test.yaml b/integration_tests/wasm/dart_test.yaml
new file mode 100644
index 0000000..14308c4
--- /dev/null
+++ b/integration_tests/wasm/dart_test.yaml
@@ -0,0 +1,5 @@
+platforms: [chrome, firefox]
+# Node doesn't work because the version available in the current Ubuntu GitHub runners is too
+# old to support WASM+GC, which would be required to run Dart tests.
+#platforms: [chrome, firefox, node]
+compilers: [dart2wasm]
diff --git a/integration_tests/wasm/mono_pkg.yaml b/integration_tests/wasm/mono_pkg.yaml
new file mode 100644
index 0000000..a8f3410
--- /dev/null
+++ b/integration_tests/wasm/mono_pkg.yaml
@@ -0,0 +1,18 @@
+# See https://pub.dev/packages/mono_repo
+
+sdk:
+- pubspec
+- dev
+
+os:
+- linux
+- windows
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+- unit_test:
+ # The config here is a regression test for https://github.com/dart-lang/test/issues/2006
+ - test: --timeout=60s
diff --git a/integration_tests/wasm/pubspec.yaml b/integration_tests/wasm/pubspec.yaml
new file mode 100644
index 0000000..370d844
--- /dev/null
+++ b/integration_tests/wasm/pubspec.yaml
@@ -0,0 +1,7 @@
+name: wasm_tests
+publish_to: none
+environment:
+ sdk: ^3.5.0
+resolution: workspace
+dev_dependencies:
+ test: any
diff --git a/integration_tests/wasm/test/hello_world_test.dart b/integration_tests/wasm/test/hello_world_test.dart
new file mode 100644
index 0000000..9ddd511
--- /dev/null
+++ b/integration_tests/wasm/test/hello_world_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2022, 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('wasm')
+// This retry is a regression test for https://github.com/dart-lang/test/issues/2006
+@Retry(2)
+library;
+
+import 'package:test/test.dart';
+
+void main() {
+ test('1 == 1', () {
+ expect(1, equals(1));
+ });
+
+ test('asserts are enabled', () {
+ expect(shouldFail, throwsA(isA<AssertionError>()));
+ });
+}
+
+void shouldFail() {
+ assert(1 == 2);
+}
diff --git a/mono_repo.yaml b/mono_repo.yaml
new file mode 100644
index 0000000..1131355
--- /dev/null
+++ b/mono_repo.yaml
@@ -0,0 +1,21 @@
+# See with https://github.com/dart-lang/mono_repo for details on this file
+self_validate: analyze_and_format
+
+github:
+ # Setting just `cron` keeps the defaults for `push` and `pull_request`
+ cron: '0 0 * * 0' # “At 00:00 (UTC) on Sunday.”
+ on_completion:
+ - name: "Notify failure"
+ runs-on: ubuntu-latest
+ # Run only if other jobs have failed and this is a push or scheduled build.
+ if: (github.event_name == 'push' || github.event_name == 'schedule') && failure()
+ steps:
+ - run: >
+ curl -H "Content-Type: application/json" -X POST -d \
+ "{'text':'Build failed! ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}'}" \
+ "${CHAT_WEBHOOK_URL}"
+ env:
+ CHAT_WEBHOOK_URL: ${{ secrets.BUILD_AND_TEST_TEAM_CHAT_WEBHOOK_URL }}
+
+merge_stages:
+- analyze_and_format
diff --git a/pkgs/checks/CHANGELOG.md b/pkgs/checks/CHANGELOG.md
new file mode 100644
index 0000000..6588352
--- /dev/null
+++ b/pkgs/checks/CHANGELOG.md
@@ -0,0 +1,54 @@
+## 0.3.1-wip
+
+- Directly compare keys across actual and expected `Map` instances when
+ checking deep collection equality and all the keys can be directly compared
+ for equality. This maintains the path into a nested collection for typical
+ cases of checking for equality against a purely value collection.
+- Always wrap Condition descriptions in angle brackets.
+- Add `containsMatchingInOrder` and `containsEqualInOrder` to replace the
+ combined functionality in `containsInOrder`.
+- Replace `pairwiseComparesTo` with `pairwiseMatches`.
+- Increase SDK constraint to ^3.5.0.
+
+## 0.3.0
+
+- **Breaking Changes**
+ - Remove the `Condition` class and the `it()` utility. Replace calls to
+ `(it()..someExpectation())` with `((it) => it.someExpectation())`.
+- Add class modifiers to restrict extension of implementation classes.
+
+## 0.2.2
+
+- Return the first failure from `softCheck` and `softCheckAsync` as
+ documented, instead of the last failure when there are multiple failures.
+- Add example `because` usage and mention the "reason" name in the migration
+ guide.
+- Add `ComparableChecks` with comparison expectations for subject types that
+ implement `Comparable`.
+
+## 0.2.1
+
+- Add a link to file issues with feedback in the README.
+
+## 0.2.0
+
+- **Breaking Changes**
+ - `checkThat` renamed to `check`.
+ - `nest` and `nestAsync` take `Iterable<String> Function()` arguments for
+ `label` instead of `String`.
+ - Async expectation extensions `completes`, `throws`, `emits`, and
+ `emitsError` no longer return a `Future<Subject>`. Instead they take an
+ optional `Condition` argument which can check expectations that would
+ have been checked on the returned subject.
+ - `nestAsync` no longer returns a `Subject`, callers must pass the
+ followup `Condition` to the nullable argument.
+ - Remove the `which` extension on `Future<Subject>`.
+ - `matches` renamed to `matchesPattern` and now accepts a `Pattern`
+ argument, instead of limiting to `RegExp`.
+- Added an example.
+- Include a stack trace in the failure description for unexpected errors from
+ Futures or Streams.
+
+## 0.1.0
+
+- Initial release.
diff --git a/pkgs/checks/LICENSE b/pkgs/checks/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/checks/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/checks/README.md b/pkgs/checks/README.md
new file mode 100644
index 0000000..5415f11
--- /dev/null
+++ b/pkgs/checks/README.md
@@ -0,0 +1,220 @@
+[](https://pub.dev/packages/checks)
+[](https://pub.dev/packages/checks/publisher)
+
+`package:checks` is a library for expressing test expectations and it features
+a literate API.
+
+## package:checks preview
+
+`package:checks` is in preview; to provide feedback on the API, please file
+[an issue][] with questions, suggestions, feature requests, or general
+feedback.
+
+For documentation about migrating from `package:matcher` to `checks`, see the
+[migration guide][].
+
+[an issue]:https://github.com/dart-lang/test/issues/new?labels=package%3Achecks&template=03_checks_feedback.md
+[migration guide]:https://github.com/dart-lang/test/blob/master/pkgs/checks/doc/migrating_from_matcher.md
+
+## Quickstart
+
+1. Add a `dev_dependency` on `checks: ^0.2.0`.
+
+1. Add an import for `package:checks/checks.dart`.
+
+1. Use `checks` in your test code:
+
+```dart
+void main() {
+ test('sample test', () {
+ // test code here
+ ...
+
+ check(actual).equals(expected);
+ check(someList).isNotEmpty();
+ check(someObject).isA<Map>();
+ check(someString)..startsWith('a')..endsWith('z')..contains('lmno');
+ });
+}
+```
+
+## Checking expectations with `checks`
+
+Expectations start with `check`. This utility returns a `Subject`, and
+expectations can be checked against the subject. Expectations are defined as
+extension methods, and different expectations will be available for subjects
+with different value types.
+
+```dart
+check(someValue).equals(expectedValue);
+check(someList).deepEquals(expectedList);
+check(someString).contains('expected pattern');
+```
+
+If a failure may not have enough context about the actual or expected values
+from the expectation calls alone, add a "Reason" in the failure message by
+passing a `because:` argument to `check()`.
+
+```dart
+check(
+ because: 'log lines must start with the severity',
+ logLines,
+).every((l) => l
+ ..anyOf([
+ (l) => l.startsWith('ERROR'),
+ (l) => l.startsWith('WARNING'),
+ (l) => l.startsWith('INFO'),
+ ]));
+```
+
+
+### Composing expectations
+
+
+Multiple expectations can be checked against the same value using cascade
+syntax. When multiple expectations are checked against a single value, a failure
+will included descriptions of the expectations that already passed.
+
+```dart
+check(someString)
+ ..startsWith('a')
+ ..endsWith('z')
+ ..contains('lmno');
+```
+
+Some nested checks may be not be possible to write with cascade syntax.
+There is a `which` utility for this use case which takes a `Condition`.
+
+```dart
+check(someString)
+ ..startsWith('a')
+ // A cascade would not be possible on `length`
+ ..length.which((l) => l
+ ..isGreatherThan(10)
+ ..isLessThan(100));
+```
+
+
+Some expectations return a `Subject` for another value derived from the original
+value, such as the `length` extension.
+
+```dart
+check(someString).length.equals(expectedLength);
+```
+
+Fields or derived values can be extracted from objects for checking further
+properties with the `has` utility.
+
+```dart
+check(someValue)
+ .has((value) => value.property, 'property')
+ .equals(expectedPropertyValue);
+```
+
+### Passing a set of expectations as an argument
+
+Some expectations take arguments which are themselves expectations to apply to
+other values. These expectations take `Condition` arguments which have the
+signature void Function(Subject)`. The conditions check expectations when they
+are called with a `Subject` argument.
+
+```dart
+check(someList).any((e) => e.isGreaterThan(0));
+```
+
+### Checking asynchronous expectations
+
+Expectation extension methods checking asynchronous behavior return a `Future`.
+The future should typically be awaited within the test body, however
+asynchronous expectations will also ensure that the test is not considered
+complete before the expectation is complete.
+Expectations with no concrete end conditions, such as an expectation that a
+future never completes, cannot be awaited and may cause a failure after the test
+has already appeared to complete.
+
+Asynchronous expectations do not return a `Subject`. When an expectation
+extracts a derived value further expectations can be checked by passing a
+`Condition`.
+
+```dart
+await check(someFuture).completes((r) => r.isGreaterThan(0));
+```
+
+Subjects for `Stream` instances must first be wrapped into a `StreamQueue` to
+allow multiple expectations to test against the stream from the same state.
+The `withQueue` extension can be used when a given stream instance only needs to
+be checked once, or if it is a broadcast stream, but if single subscription
+stream needs to have multiple expectations checked separately it should be
+wrapped with a `StreamQueue`.
+
+```dart
+await check(someStream).withQueue.inOrder([
+ (s) => s.emits((e) => e.equals(1)),
+ (s) => s.emits((e) => e.equals(2)),
+ (s) => s.emits((e) => e.equals(3)),
+ (s) => s.isDone(),
+]);
+
+var someQueue = StreamQueue(someOtherStream);
+await check(someQueue).emits((e) => e.equals(1));
+// do something
+await check(someQueue).emits((e) => e.equals(2));
+// do something
+```
+
+
+## Writing custom expectations
+
+Expectations are written as extensions on `Subject` with specific generics. The
+library `package:checks/context.dart` gives access to a `context` getter on
+`Subject` which offers capabilities for defining expectations on the subject's
+value.
+
+The `Context` allows checking an expectation with `expect`, `expectAsync` and
+`expectUnawaited`, or extracting a derived value for performing other checks
+with `nest` and `nestAsync`. Failures are reported by returning a `Rejection`,
+or an `Extracted.rejection`, extensions should avoid throwing exceptions.
+
+Descriptions of the clause checked by an expectations are passed through a
+separate callback from the predicate which checks the value. Nesting calls are
+made with a label directly. When there are no failures the clause callbacks are
+not called. When a condition callback is described, the clause callbacks are
+called, but the predicate callbacks are not called. Conditions can be checked
+against values without throwing an exception using `softCheck` or
+`softCheckAsync`.
+
+```dart
+extension CustomChecks on Subject<CustomType> {
+ void someExpectation() {
+ context.expect(() => ['meets this expectation'], (actual) {
+ if (_expectationIsMet(actual)) return null;
+ return Rejection(which: ['does not meet this expectation']);
+ });
+ }
+
+ Subject<Foo> get someDerivedValue =>
+ context.nest(() => ['has someDerivedValue'], (actual) {
+ if (_cannotReadDerivedValue(actual)) {
+ return Extracted.rejection(which: ['cannot read someDerivedValue']);
+ }
+ return Extracted.value(_readDerivedValue(actual));
+ });
+
+ // for field reads that will not get rejected, use `has`
+ Subject<Bar> get someField => has((a) => a.someField, 'someField');
+}
+```
+
+Extensions may also compose existing expectations under a single name. When
+such expectations fail, the test output will refer to the individual
+expectations that were called.
+
+```dart
+extension ComposedChecks on Subject<Iterable> {
+ void hasLengthInRange(int min, int max) {
+ length
+ ..isGreaterThan(min)
+ ..isLessThan(max);
+ }
+}
+```
diff --git a/pkgs/checks/doc/migrating_from_matcher.md b/pkgs/checks/doc/migrating_from_matcher.md
new file mode 100644
index 0000000..b358e63
--- /dev/null
+++ b/pkgs/checks/doc/migrating_from_matcher.md
@@ -0,0 +1,203 @@
+## Migrating from package:matcher
+
+`package:checks` is currently in preview. Once this package reaches a stable
+version, it will be the recommended package by the Dart team to use for most
+tests.
+
+[`package:matcher`][matcher] is the legacy package with an API exported from
+`package:test/test.dart` and `package:test/expect.dart`.
+
+**Do I have to migrate all at once?** No. `package:matcher` will be compatible
+with `package:checks`, and old tests can continue to use matchers. Test cases
+within the same file can use a mix of `expect` and `check`.
+
+**_Should_ I migrate all at once?** Probably not, it depends on your tolerance
+for having tests use a mix of APIs. As you add new tests, or need to make
+updates to existing tests, using `checks` will make testing easier. Tests which
+are stable and passing will not get significant benefits from a migration.
+
+**Do I need to migrate at all?** No. When `package:test`stops exporting
+these members it will be possible to add a dependency on `package:matcher` and
+continue to use them. `package:matcher` will continue to be available.
+
+**Why is the Dart team adding a second framework?** The `matcher` package has a
+design which is fundamentally incompatible with using static types to validate
+correct use. With an entirely new design, the static types in `checks` give
+confidence that the expectation is appropriate for the value, and can narrow
+autocomplete choices in the IDE for a better editing experience. The clean break
+from the legacy implementation and API also gives an opportunity to make small
+behavior and signature changes to align with modern Dart idioms.
+
+**Should I start using checks right away?** There is still a
+high potential for minor or major breaking changes during the preview window.
+Once this package is stable, yes! The experience of using `checks` improves on
+`matcher`. See some of the [improvements to look forward to in checks
+below](#improvements-you-can-expect).
+
+[matcher]: https://pub.dev/packages/matcher
+
+## Trying Checks as a Preview
+
+1. Add a `dev_dependency` on `checks: ^0.2.0`.
+
+1. Replace the existing `package:test/test.dart` import with
+ `package:test/scaffolding.dart`.
+
+1. Add an import to `package:checks/checks.dart`.
+
+1. For an incremental migration within the test, add an import to
+ `package:test/expect.dart`. Remove it to surface errors in tests that still
+ need to be migrated, or keep it in so the tests work without being fully
+ migrated.
+
+1. Migrate the test cases.
+
+## Migrating from Matchers
+
+Replace calls to `expect` or `expectLater` with a call to `check` passing the
+first argument.
+When a direct replacement is available, change the second argument from calling
+a function returning a Matcher, to calling the relevant extension method on the
+`Subject`.
+
+Whenever you see a bare non-matcher value argument for `expected`, assume it
+should use the `equals` expectation, although take care when the subject is a
+collection.
+See below, `.equals` may not always be the correct replacement in
+`package:checks`.
+
+```dart
+expect(actual, expected);
+check(actual).equals(expected);
+// or maybe
+check(actualCollection).deepEquals(expected);
+
+await expectLater(actual, completes());
+await check(actual).completes();
+```
+
+If you use the `reason` argument to `expect`, rename it to `because`.
+
+```dart
+expect(actual, expectation(), reason: 'some explanation');
+check(because: 'some explanation', actual).expectation();
+```
+
+### Differences in behavior from matcher
+
+- The `equals` Matcher performed a deep equality check on collections.
+ `.equals()` expectation will only correspond to [operator ==] so some tests
+ may need to replace `.equals()` with `.deepEquals()`.
+- Streams must be explicitly wrapped into a `StreamQueue` before they can be
+ tested for behavior. Use `check(actualStream).withQueue`.
+- `emitsAnyOf` is `Subject<StreamQueue>.anyOf`. `emitsInOrder` is `inOrder`.
+ The arguments are `FutureOr<void> Function(Subject<StreamQueue>)` and match
+ a behavior of the entire stream. In `matcher` the elements to expect could
+ have been a bare value to check for equality, a matcher for the emitted
+ value, or a matcher for the entire queue which would match multiple values.
+ Use `(s) => s.emits((e) => e.interestingCheck())` to check the emitted
+ elements.
+- In `package:matcher` the [`matches` Matcher][matches] converted a `String`
+ argument into a `Regex`, so `matches(r'\d')` would match the value `'1'`.
+ This was potentially confusing, because even though `String` is a subtype of
+ `Pattern`, it wasn't used as a pattern directly.
+ With `matchesPattern` a `String` argument is used as a `Pattern` and
+ comparison uses [`String.allMatches`][allMatches].
+ For backwards compatibility change `matches(regexString)` to
+ `matchesPattern(RegExp(regexString))`.
+- The `TypeMatcher.having` API is replace by the more general`.has`. While
+ `.having` could only be called on a `TypeMatcher` using `.isA`, `.has` works
+ on any `Subject`. `CoreChecks.has` takes 1 fewer arguments - instead of
+ taking the last argument, a `matcher` to apply to the field, it returns a
+ `Subject` for the field.
+
+[matches]:https://pub.dev/documentation/matcher/latest/matcher/Matcher/matches.html
+[allMatches]:https://api.dart.dev/stable/2.19.1/dart-core/Pattern/allMatches.html
+
+### Matchers with replacements under a different name
+
+- `anyElement` -> `Subject<Iterable>.any`
+- `everyElement` -> `Subject<Iterable>.every`
+- `completion(Matcher)` -> `completes(conditionCallback)`
+- `containsPair(key, value)` -> Use `Subject<Map>[key].equals(value)`
+- `hasLength(expected)` -> `length.equals(expected)`
+- `isNot(Matcher)` -> `not(conditionCallback)`
+- `pairwiseCompare` -> `pairwiseMatches`
+- `same` -> `identicalTo`
+- `stringContainsInOrder` -> `Subject<String>.containsInOrder`
+- `containsAllInOrder(iterable)` ->
+ `Subject<Iterable>.containsMatchingInOrder(iterable)` to compare with
+ conditions other than equals,
+ `Subject<Iterable>.containsEqualInOrder(iterable)` to compare each index
+ with the equality operator (`==`).
+
+### Members from `package:test/expect.dart` without a direct replacement
+
+- `checks` does not ship with any type checking matchers for specific types.
+ Instead of, for example, `isArgumentError` use `isA<ArgumentError>`, and
+ similary `throws<ArgumentError>` over `throwsArgumentError`.
+- `anything`. When a condition callback is needed that should accept any
+ value, pass `(_) {}`.
+- Specific numeric comparison - `isNegative`, `isPositive`, `isZero` and their
+ inverses. Use `isLessThan`, `isGreaterThan`, `isLessOrEqual`, and
+ `isGreaterOrEqual` with appropriate numeric arguments.
+- Numeric range comparison, `inClosedOpenRange`, `inExclusiveRange`,
+ `inInclusiveRange`, `inOpenClosedRange`. Use cascades to chain a check for
+ both ends of the range onto the same subject.
+- `containsOnce`: TODO add missing expectation
+- `emitsInAnyOrder`: TODO add missing expectation
+- `expectAsync` and `expectAsyncUntil`. Continue to import
+ `package:test/expect.dart` for these APIs.
+- `isIn`: TODO add missing expectation
+- `orderedEquals`: Use `deepEquals`. If the equality needs to specifically
+ *not* be deep equality (this is unusual, nested collections are unlikely to
+ have a meaningful equality), force using `operator ==` at the first level
+ with `.deepEquals(expected.map((e) => (Subject<Object?> s) => s.equals(e)))`;
+- `prints`: TODO add missing expectation? Is this one worth replacing?
+- `predicate`: TODO add missing expectation
+
+## Improvements you can expect
+
+Expectations are statically restricted to those which are appropriate for the
+type. So while the following is statically allowed with `matcher` but always
+fails at runtime, the expectation cannot be written at all with `checks`.
+
+```dart
+expect(1, contains(1)); // No static error, always fails
+check(1).contains(1); // Static error. The method 'contains' isn't defined
+```
+
+These static restrictions also improve the relevance of IDE autocomplete
+suggestions. While editing with the cursor at `_`, the suggestions provided
+in the `matcher` example can include _any_ top level element including matchers
+appropriate for other types of value, type names, and top level definitions from
+other packages. With the cursor following a `.` in the `checks` example the
+suggestions will only be expectations or utilities appropriate for the value
+type.
+
+```dart
+expect(actual, _ // many unrelated suggestions
+check(actual)._ // specific suggestions
+```
+
+Asynchronous matchers in `matcher` are a subtype of synchronous matchers, but do
+not satisfy the same behavior contract. Some APIs which use a matcher could not
+validate whether it would satisfy the behavior it needs, and it could result in
+a false success, false failure, or misleading errors. APIs which correctly use
+asynchronous matchers need to do a type check and change their interaction based
+on the runtime type. Asynchronous expectations in `checks` are refused at
+runtime when a synchronous answer is required. The error will help solve the
+specific misuse, instead of resulting in a confusing error, or worse a missed
+failure. The reason for the poor compatibility in `matcher` is due to some
+history of implementation - asynchronous matchers were written in `test`
+alongside `expect`, and synchronous matchers have no dependency on the
+asynchronous implementation.
+
+Asynchronous expectations always return a `Future`, and with the
+[`unawaited_futures` lint][unawaited lint] should more safely ensure that
+asynchronous expectation work is completed within the test body. With `matcher`
+it was up to the author to correctly use `await expecLater` for asynchronous
+cases, and `expect` for synchronous cases, and if `expect` was used with an
+asynchronous matcher the expectation could fail at any point.
+
+[unawaited lint]: https://dart.dev/lints/unawaited_futures
diff --git a/pkgs/checks/example/example.dart b/pkgs/checks/example/example.dart
new file mode 100644
index 0000000..88bec0b
--- /dev/null
+++ b/pkgs/checks/example/example.dart
@@ -0,0 +1,26 @@
+// 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+void main() {
+ test('sample test', () {
+ final someValue = 5;
+ check(someValue).equals(5);
+
+ final someList = [1, 2, 3, 4, 5];
+ check(someList).deepEquals([1, 2, 3, 4, 5]);
+
+ final someString = 'abcdefghijklmnopqrstuvwxyz';
+
+ check(
+ because: 'it should contain the beginning, middle and end',
+ someString,
+ )
+ ..startsWith('a')
+ ..endsWith('z')
+ ..contains('lmno');
+ });
+}
diff --git a/pkgs/checks/lib/checks.dart b/pkgs/checks/lib/checks.dart
new file mode 100644
index 0000000..db8b18e
--- /dev/null
+++ b/pkgs/checks/lib/checks.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2022, 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.
+
+export 'src/checks.dart'
+ show AsyncCondition, Condition, SkipExtension, Subject, check;
+export 'src/extensions/async.dart'
+ show FutureChecks, StreamChecks, WithQueueExtension;
+export 'src/extensions/core.dart'
+ show BoolChecks, ComparableChecks, CoreChecks, NullableChecks;
+export 'src/extensions/function.dart' show FunctionChecks;
+export 'src/extensions/iterable.dart' show IterableChecks;
+export 'src/extensions/map.dart' show MapChecks;
+export 'src/extensions/math.dart' show NumChecks;
+export 'src/extensions/string.dart' show StringChecks;
diff --git a/pkgs/checks/lib/context.dart b/pkgs/checks/lib/context.dart
new file mode 100644
index 0000000..cebba71
--- /dev/null
+++ b/pkgs/checks/lib/context.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2022, 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.
+
+export 'src/checks.dart'
+ show
+ AsyncCondition,
+ CheckFailure,
+ Condition,
+ Context,
+ ContextExtension,
+ Extracted,
+ FailureDetail,
+ Rejection,
+ Subject,
+ describe,
+ describeAsync,
+ softCheck,
+ softCheckAsync;
+export 'src/describe.dart'
+ show escape, indent, literal, postfixLast, prefixFirst;
diff --git a/pkgs/checks/lib/src/checks.dart b/pkgs/checks/lib/src/checks.dart
new file mode 100644
index 0000000..e1e4bcb
--- /dev/null
+++ b/pkgs/checks/lib/src/checks.dart
@@ -0,0 +1,1009 @@
+// Copyright (c) 2022, 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 'package:meta/meta.dart' as meta;
+import 'package:test_api/hooks.dart';
+
+import 'describe.dart';
+import 'extensions/async.dart';
+import 'extensions/core.dart';
+import 'extensions/iterable.dart';
+
+/// A target for checking expectations against a value in a test.
+///
+/// A subject my have a real value, in which case the expectations can be
+/// validated or rejected; or it may be a placeholder, in which case
+/// expectations describe what would be checked but cannot be rejected.
+///
+/// Expectation methods are defined in extensions `on Subject`, specialized on
+/// the generic [T].
+/// Expectation extension methods can use the [ContextExtension] to interact
+/// with the [Context] for this subject.
+///
+/// Create a subject that throws an exception for missed expectations with the
+/// [check] function.
+final class Subject<T> {
+ final Context<T> _context;
+ Subject._(this._context);
+}
+
+/// A callback that synchronously checks expectations against a subject.
+///
+/// Asynchronous expectations should not be used within a `Condition` callback.
+typedef Condition<T> = void Function(Subject<T>);
+
+/// A callback that asynchronously checks expectations against a subject.
+///
+/// Any expectations may be used within an `AsyncCondition` callback.
+typedef AsyncCondition<T> = FutureOr<void> Function(Subject<T>);
+
+extension SkipExtension<T> on Subject<T> {
+ /// Mark the currently running test as skipped and return a [Subject] that
+ /// will ignore all expectations.
+ ///
+ /// Any expectations against the return value will not be checked and will not
+ /// be included in the "Expected" or "Actual" string representations of a
+ /// failure.
+ ///
+ /// ```dart
+ /// check(actual)
+ /// ..stillChecked()
+ /// ..skip('reason the expectation is temporarily not met').notChecked();
+ /// ```
+ ///
+ /// If `skip` is used in a callback passed to `softCheck` or `describe` it
+ /// will still mark the test as skipped, even though failing the expectation
+ /// would not have otherwise caused the test to fail.
+ Subject<T> skip(String message) {
+ TestHandle.current.markSkipped(message);
+ return Subject._(_SkippedContext());
+ }
+}
+
+/// Creates a [Subject] that can be used to validate expectations against
+/// [value], with an exception upon a failed expectation.
+///
+/// Expectations that are not satisfied throw a [TestFailure] to interrupt the
+/// currently running test and mark it as failed.
+///
+/// If [because] is passed it will be included as a "Reason:" line in failure
+/// messages.
+///
+/// ```dart
+/// check(actual).equals(expected);
+/// ```
+@meta.useResult
+Subject<T> check<T>(T value, {String? because}) => Subject._(_TestContext._root(
+ value: _Present(value),
+ // TODO - switch between "a" and "an"
+ label: 'a $T',
+ fail: (f) {
+ final which = f.rejection.which;
+ throw TestFailure([
+ ...prefixFirst('Expected: ', f.detail.expected),
+ ...prefixFirst('Actual: ', f.detail.actual),
+ ...indent(
+ prefixFirst('Actual: ', f.rejection.actual), f.detail.depth),
+ if (which != null && which.isNotEmpty)
+ ...indent(prefixFirst('Which: ', which), f.detail.depth),
+ if (because != null) 'Reason: $because',
+ ].join('\n'));
+ },
+ allowAsync: true,
+ allowUnawaited: true,
+ ));
+
+/// Checks whether [value] satisfies all expectations invoked in [condition],
+/// without throwing an exception.
+///
+/// Returns `null` if all expectations are satisfied, otherwise returns the
+/// [CheckFailure] for the first expectation that fails.
+///
+/// Asynchronous expectations are not allowed in [condition] and will cause a
+/// runtime error if they are used.
+CheckFailure? softCheck<T>(T value, Condition<T> condition) {
+ CheckFailure? failure;
+ final subject = Subject<T>._(_TestContext._root(
+ value: _Present(value),
+ fail: (f) {
+ failure ??= f;
+ },
+ allowAsync: false,
+ allowUnawaited: false,
+ ));
+ condition(subject);
+ return failure;
+}
+
+/// Checks whether [value] satisfies all expectations invoked in [condition],
+/// without throwing an exception.
+///
+/// The future will complete to `null` if all expectations are satisfied,
+/// otherwise it will complete to the [CheckFailure] for the first expectation
+/// that fails.
+///
+/// In contrast to [softCheck], asynchronous expectations are allowed in
+/// [condition].
+Future<CheckFailure?> softCheckAsync<T>(
+ T value, AsyncCondition<T> condition) async {
+ CheckFailure? failure;
+ final subject = Subject<T>._(_TestContext._root(
+ value: _Present(value),
+ fail: (f) {
+ failure ??= f;
+ },
+ allowAsync: true,
+ allowUnawaited: false,
+ ));
+ await condition(subject);
+ return failure;
+}
+
+/// Creates a description of the expectations checked by [condition].
+///
+/// The strings are individual lines of a description.
+/// The description of an expectation may be one or more adjacent lines.
+///
+/// Matches the "Expected: " lines in the output of a failure message if a value
+/// did not meet the last expectation in [condition], without the first labeled
+/// line.
+///
+/// Asynchronous expectations are not allowed in [condition], for async
+/// conditions use [describeAsync].
+Iterable<String> describe<T>(Condition<T> condition) {
+ final context = _TestContext<T>._root(
+ value: _Absent(),
+ fail: (_) {
+ throw UnimplementedError();
+ },
+ allowAsync: false,
+ allowUnawaited: true,
+ );
+ condition(Subject._(context));
+ return context.detail(context).expected.skip(1);
+}
+
+/// Creates a description of the expectations checked by [condition].
+///
+/// The strings are individual lines of a description.
+/// The description of an expectation may be one or more adjacent lines.
+///
+/// Matches the "Expected: " lines in the output of a failure message if a value
+/// did not meet the last expectation in [condition], without the first labeled
+/// line.
+///
+/// In contrast to [describe], asynchronous expectations are allowed in
+/// [condition].
+Future<Iterable<String>> describeAsync<T>(AsyncCondition<T> condition) async {
+ final context = _TestContext<T>._root(
+ value: _Absent(),
+ fail: (_) {
+ throw UnimplementedError();
+ },
+ allowAsync: true,
+ allowUnawaited: true,
+ );
+ await condition(Subject._(context));
+ return context.detail(context).expected.skip(1);
+}
+
+extension ContextExtension<T> on Subject<T> {
+ /// The expectations and nesting context for this subject.
+ Context<T> get context => _context;
+}
+
+/// The context for a [Subject] that allows asserting expectations and creating
+/// nested subjects.
+///
+/// A [Subject] is the target for checking expectations in a test.
+/// Every subject has a [Context] which holds the "actual" value, tracks how the
+/// value was obtained, and can check expectations about the value.
+///
+/// The user focused APIs called within tests are expectation extension methods
+/// written in an extension `on Subject`, typically specialized to a specific
+/// generic.
+///
+/// Expectation extension methods will make a call to one of the APIs on the
+/// subject's [Context], and can perform one of two types of operations:
+///
+/// - Expect something of the current value (such as [CoreChecks.equals] or
+/// [IterableChecks.contains]) by calling [expect], [expectAsync], or
+/// [expectUnawaited].
+/// - Expect that a new subject can be extracted from the current value (such
+/// as [CoreChecks.has] or [FutureChecks.completes]) by calling [nest] or
+/// [nestAsync].
+///
+///
+/// Whichever type of operation, an expectation extension method provides two
+/// callbacks.
+/// The first callback is an `Iterable<String> Function()` returning a
+/// description of the expectation.
+/// The second callback always takes the actual value as an argument, and the
+/// specific signature varies by operation.
+///
+///
+/// In expectation extension methods calling [expect], [expectAsync], or
+/// [expectUnawaited], the `predicate` callback can report a [Rejection] if the
+/// value fails to satisfy the expectation.
+/// The description will be passed in a "clause" callback.
+/// {@template clause_description}
+/// The clause callback returns a description of what is checked which stands
+/// on its own.
+/// For instance the `is equal to <1>` in:
+///
+/// ```
+/// Expected: a int that:
+/// is equal to <1>
+/// ```
+/// {@endtemplate}
+///
+///
+/// In expectation extension methods calling [nest] or [nestAsync], the
+/// `extract` callback can return a [Extracted.rejection] if the value fails to
+/// satisfy an expectation which disallows extracting the value, or an
+/// [Extracted.value] to become the value in a nested subject.
+/// The description will be passed in a "label" callback.
+/// {@template label_description}
+/// The label callback returns a description of the extracted subject as it
+/// relates to the original subject.
+/// For instance the `completes to a value` in:
+///
+/// ```
+/// Expected a Future<int> that:
+/// completes to a value that:
+/// is equal to <1>
+/// ```
+///
+/// A label should also be sensible when it is read as a clause.
+/// If no further expectations are checked on the extracted subject, or if the
+/// extraction is rejected, the "that:" is omitted in the output.
+///
+/// ```
+/// Expected a Future<int> that:
+/// completes to a value
+/// ```
+/// {@endtemplate}
+///
+///
+/// A rejection carries two descriptions, one description of the "actual" value
+/// that was tested, and an optional "which" with further details about how the
+/// result different from the expectation.
+/// If the "actual" argument is omitted it will be filled with a representation
+/// of the value passed to the expectation callback formatted with [literal].
+/// If an expectation extension method is written on a type of subject without a
+/// useful `toString()`, the rejection can provide a string representation to
+/// use instead.
+/// The "which" argument may be omitted if the reason is very obvious based on
+/// the clause and "actual" description, but most expectations should include a
+/// "which".
+///
+/// The behavior of a context following a rejection depends on the source of the
+/// [Subject].
+///
+/// When an expectation is rejected for a [check] subject, an exception is
+/// thrown to interrupt the test, so no further checks should happen. The
+/// failure message will include:
+/// - An "Expected" section with descriptions of all the expectations that
+/// were checked, including the ones that passed, and the last one that
+/// failed.
+/// - An "Actual" section, which may be the description directly from the
+/// [Rejection] if the failure was on the root subject, or may start with a
+/// partial version of the "Expected" description up to the label for the
+/// nesting subject that saw a failure, then the "actual" from the rejection.
+/// - A "Which" description from the rejection, if it was included.
+///
+/// For example, if a failure happens on the root subject, the "actual" is taken
+/// directly from the rejection.
+///
+/// ```
+/// Expected: a Future<int> that:
+/// completes to a value
+/// Actual: a future that completes as an error
+/// Which: threw <UnimplementedError> at:
+/// <stack trace>
+/// ```
+///
+/// But if the failure happens on a nested subject, the actual starts with a
+/// description of the nesting or non-nesting expectations that succeeded, up
+/// to nesting point of the failure, then the "actual" and "which" from the
+/// rejection are indented to that level of nesting.
+///
+/// ```
+/// Expected: a Future<int> that:
+/// completes to a value that:
+/// equals <1>
+/// Actual: a Future<int> that:
+/// completes to a value that:
+/// Actual: <0>
+/// Which: are not equal
+/// ```
+///
+/// ```dart
+/// extension CustomChecks on Subject<CustomType> {
+/// void someExpectation() {
+/// context.expect(() => ['meets this expectation'], (actual) {
+/// if (_expectationIsMet(actual)) return null;
+/// return Rejection(which: ['does not meet this expectation']);
+/// });
+/// }
+///
+/// Subject<Foo> get someDerivedValue =>
+/// context.nest('has someDerivedValue', (actual) {
+/// if (_cannotReadDerivedValue(actual)) {
+/// return Extracted.rejection(which: ['cannot read someDerivedValue']);
+/// }
+/// return Extracted.value(_readDerivedValue(actual));
+/// });
+///
+/// // for field reads that will not get rejected, use `has`
+/// Subject<Bar> get someField => has((a) => a.someField, 'someField');
+/// }
+/// ```
+///
+/// When an expectation is rejected for a subject within a call to [softCheck]
+/// or [softCheckAsync] a [CheckFailure] will be returned with the rejection, as
+/// well as a [FailureDetail] which could be used to format the same failure
+/// message thrown by the [check] subject.
+///
+/// {@template callbacks_may_be_unused}
+/// The description of an expectation may never be shown to the user, so the
+/// callback may never be invoked.
+/// If all the conditions on a subject succeed, or if the failure detail for a
+/// failed [softCheck] is never read, the descriptions will be unused.
+/// String formatting for the descriptions should be performed in the callback,
+/// not ahead of time.
+///
+///
+/// The context for a subject may hold a real "actual" value to test against, or
+/// it may have a placeholder within a call to [describe].
+/// A context with a placeholder value will not invoke the callback to check
+/// expectations.
+///
+/// If both callbacks are invoked, the description callback will always be
+/// called strictly after the expectation callback is called.
+///
+/// Callbacks passed to a context should not throw.
+/// {@endtemplate}
+///
+///
+/// Some contexts disallow certain interactions.
+/// {@template async_limitations}
+/// Calls to [expectAsync] or [nestAsync] must not be performed by a condition
+/// callback passed to [softCheck] or [describe].
+/// Use [softCheckAsync] or [describeAsync] for any condition which checks async
+/// expectations.
+/// {@endtemplate}
+/// {@template unawaited_limitations}
+/// Calls to [expectUnawaited] may not be performed by a condition callback
+/// passed to [softCheck] or [softCheckAsync].
+/// {@endtemplate}
+///
+/// Expectation extension methods can access the context for the subject with
+/// the [ContextExtension].
+///
+/// {@template description_lines}
+/// Description callbacks return an `Iterable<String>` where each element is a
+/// line in the output. Individual elements should not contain newlines.
+/// Utilities such as [prefixFirst], [postfixLast], and [literal] may be useful
+/// to format values which are potentially multiline.
+/// {@endtemplate}
+abstract final class Context<T> {
+ /// Expect that [predicate] will not return a [Rejection] for the checked
+ /// value.
+ ///
+ /// {@macro clause_description}
+ ///
+ /// {@macro description_lines}
+ ///
+ /// {@macro callbacks_may_be_unused}
+ ///
+ /// ```dart
+ /// void someExpectation() {
+ /// context.expect(() => ['meets this expectation'], (actual) {
+ /// if (_expectationIsMet(actual)) return null;
+ /// return Rejection(which: ['does not meet this expectation']);
+ /// });
+ /// }
+ /// ```
+ void expect(
+ Iterable<String> Function() clause, Rejection? Function(T) predicate);
+
+ /// Expect that [predicate] will not result in a [Rejection] for the checked
+ /// value.
+ ///
+ /// {@macro clause_description}
+ ///
+ /// {@macro description_lines}
+ ///
+ /// {@macro callbacks_may_be_unused}
+ ///
+ /// {@macro async_limitations}
+ ///
+ /// ```dart
+ /// extension CustomChecks on Subject<CustomType> {
+ /// Future<void> someAsyncExpectation() async {
+ /// await context.expectAsync(() => ['meets this async expectation'],
+ /// (actual) async {
+ /// if (await _expectationIsMet(actual)) return null;
+ /// return Rejection(which: ['does not meet this async expectation']);
+ /// });
+ /// }
+ /// }
+ /// ```
+ Future<void> expectAsync(Iterable<String> Function() clause,
+ FutureOr<Rejection?> Function(T) predicate);
+
+ /// Expect that [predicate] will not invoke the passed callback with a
+ /// [Rejection] at any point.
+ ///
+ /// In contrast to [expectAsync], a rejection is reported through a
+ /// callback instead of through a returned Future. The callback may be invoked
+ /// at any point that the failure surfaces.
+ ///
+ /// This may be useful for a condition checking that some event _never_
+ /// happens. If there is no specific point where it is know to be safe to stop
+ /// listening for the event, there is no way to complete a returned future and
+ /// consider the check "complete".
+ ///
+ /// {@macro clause_description}
+ ///
+ /// {@macro description_lines}
+ ///
+ /// {@macro callbacks_may_be_unused}
+ ///
+ /// {@macro unawaited_limitations}
+ /// The only useful effect of a late rejection is to throw a [TestFailure]
+ /// when used with a [check] subject. Most conditions should prefer to use
+ /// [expect] or [expectAsync].
+ ///
+ /// ```dart
+ /// void someUnawaitableExpectation() async {
+ /// await context.expectUnawaited(
+ /// () => ['meets this unawaitable expectation'], (actual, reject) {
+ /// final failureSignal = _completeIfFailed(actual);
+ /// unawaited(failureSignal.then((_) {
+ /// reject(Reject(
+ /// which: ['unexpectedly failed this unawaited expectation']));
+ /// }));
+ /// });
+ /// }
+ /// ```
+ void expectUnawaited(Iterable<String> Function() clause,
+ void Function(T, void Function(Rejection)) predicate);
+
+ /// Extract a property from the value for further checking.
+ ///
+ /// If the property cannot be extracted, [extract] should return an
+ /// [Extracted.rejection] describing the problem. Otherwise it should return
+ /// an [Extracted.value].
+ ///
+ /// Subsequent expectations can be checked for the extracted value on the
+ /// returned [Subject].
+ ///
+ /// {@macro label_description}
+ ///
+ /// If [atSameLevel] is true then the returned [Extracted.value] should hold
+ /// the same instance as the passed value, or an object which is is equivalent
+ /// but has a type that is more convenient to test.
+ /// In this case expectations applied to the returned [Subject] will behave as
+ /// if they were applied to the subject for this context.
+ /// The [label] will be used as if it were a "clause" argument passed to
+ /// [expect].
+ /// If the label returns an empty iterable, the clause will be omitted.
+ /// The label should only be left empty if the value extraction cannot be
+ /// rejected.
+ ///
+ /// {@macro description_lines}
+ ///
+ /// {@macro callbacks_may_be_unused}
+ ///
+ /// ```dart
+ /// Subject<Foo> get someDerivedValue =>
+ /// context.nest(() => ['has someDerivedValue'], (actual) {
+ /// if (_cannotReadDerivedValue(actual)) {
+ /// return Extracted.rejection(
+ /// which: ['cannot read someDerivedValue']);
+ /// }
+ /// return Extracted.value(_readDerivedValue(actual));
+ /// });
+ /// ```
+ Subject<R> nest<R>(
+ Iterable<String> Function() label, Extracted<R> Function(T) extract,
+ {bool atSameLevel = false});
+
+ /// Extract an asynchronous property from the value for further checking.
+ ///
+ /// If the property cannot be extracted, [extract] should return an
+ /// [Extracted.rejection] describing the problem. Otherwise it should return
+ /// an [Extracted.value].
+ ///
+ /// In contrast to [nest], subsequent expectations need to be passed in
+ /// [nestedCondition] which will be applied to the subject for the extracted
+ /// value.
+ ///
+ /// {@macro label_description}
+ ///
+ /// {@macro description_lines}
+ ///
+ /// {@macro callbacks_may_be_unused}
+ ///
+ /// {@macro async_limitations}
+ ///
+ /// ```dart
+ /// Future<void> someAsyncResult(
+ /// [AsyncCondition<Result> resultCondition]) async {
+ /// await context.nestAsync(() => ['has someAsyncResult'], (actual) async {
+ /// if (await _asyncOperationFailed(actual)) {
+ /// return Extracted.rejection(which: ['cannot read someAsyncResult']);
+ /// }
+ /// return Extracted.value(await _readAsyncResult(actual));
+ /// }, resultCondition);
+ /// }
+ /// ```
+ Future<void> nestAsync<R>(
+ Iterable<String> Function() label,
+ FutureOr<Extracted<R>> Function(T) extract,
+ AsyncCondition<R>? nestedCondition);
+}
+
+/// A property extracted from a value being checked, or a rejection.
+final class Extracted<T> {
+ final Rejection? _rejection;
+ final T? _value;
+
+ /// Creates a rejected extraction to indicate a failure trying to read the
+ /// value.
+ ///
+ /// When a nesting is rejected with an omitted or empty [actual] argument, it
+ /// will be filled in with the [literal] representation of the value.
+ Extracted.rejection(
+ {Iterable<String> actual = const [], Iterable<String>? which})
+ : _rejection = Rejection(actual: actual, which: which),
+ _value = null;
+ Extracted.value(T this._value) : _rejection = null;
+
+ Extracted._(Rejection this._rejection) : _value = null;
+
+ Extracted<R> _map<R>(R Function(T) transform) {
+ final rejection = _rejection;
+ if (rejection != null) return Extracted._(rejection);
+ return Extracted.value(transform(_value as T));
+ }
+
+ Extracted<T> _fillActual(Object? actual) => _rejection == null ||
+ _rejection.actual.isNotEmpty
+ ? this
+ : Extracted.rejection(actual: literal(actual), which: _rejection.which);
+}
+
+abstract interface class _Optional<T> {
+ R? apply<R extends FutureOr<Rejection?>>(R Function(T) callback);
+ Future<Extracted<_Optional<R>>> mapAsync<R>(
+ FutureOr<Extracted<R>> Function(T) transform);
+ Extracted<_Optional<R>> map<R>(Extracted<R> Function(T) transform);
+}
+
+class _Present<T> implements _Optional<T> {
+ final T value;
+ _Present(this.value);
+
+ @override
+ R? apply<R extends FutureOr<Rejection?>>(R Function(T) c) => c(value);
+
+ @override
+ Future<Extracted<_Present<R>>> mapAsync<R>(
+ FutureOr<Extracted<R>> Function(T) transform) async {
+ final transformed = await transform(value);
+ return transformed._map(_Present.new);
+ }
+
+ @override
+ Extracted<_Present<R>> map<R>(Extracted<R> Function(T) transform) =>
+ transform(value)._map(_Present.new);
+}
+
+class _Absent<T> implements _Optional<T> {
+ @override
+ R? apply<R extends FutureOr<Rejection?>>(R Function(T) c) => null;
+
+ @override
+ Future<Extracted<_Absent<R>>> mapAsync<R>(
+ FutureOr<Extracted<R>> Function(T) transform) async =>
+ Extracted.value(_Absent<R>());
+
+ @override
+ Extracted<_Absent<R>> map<R>(FutureOr<Extracted<R>> Function(T) transform) =>
+ Extracted.value(_Absent<R>());
+}
+
+final class _TestContext<T> implements Context<T>, _ClauseDescription {
+ final _Optional<T> _value;
+
+ /// A reference to find the root context which this context is nested under.
+ ///
+ /// null only for the root context.
+ final _TestContext<dynamic>? _parent;
+
+ final List<_ClauseDescription> _clauses;
+ final List<_TestContext> _aliases;
+
+ final void Function(CheckFailure) _fail;
+
+ final bool _allowAsync;
+ final bool _allowUnawaited;
+
+ /// A callback that returns a label for this context.
+ ///
+ /// If this context is the root the label should return a phrase like
+ /// "a List" in
+ ///
+ /// ```
+ /// Expected: a List that:
+ /// ```
+ ///
+ /// If this context is nested under another context the lable should return a
+ /// phrase like "completes to a value" in
+ ///
+ ///
+ /// ```
+ /// Expected: a Future<int> that:
+ /// completes to a value that:
+ /// ```
+ ///
+ /// In cases where a nested context does not have any expectations checked on
+ /// it, the "that:" will be will be omitted.
+ final Iterable<String> Function() _label;
+
+ static Iterable<String> _emptyLabel() => const [];
+
+ /// Create a context appropriate for a subject which is not nested under any
+ /// other subject.
+ _TestContext._root({
+ required _Optional<T> value,
+ required void Function(CheckFailure) fail,
+ required bool allowAsync,
+ required bool allowUnawaited,
+ String? label,
+ }) : _value = value,
+ _label = (() => [label ?? '']),
+ _fail = fail,
+ _allowAsync = allowAsync,
+ _allowUnawaited = allowUnawaited,
+ _parent = null,
+ _clauses = [],
+ _aliases = [];
+
+ _TestContext._alias(_TestContext original, this._value)
+ : _parent = original,
+ _clauses = original._clauses,
+ _aliases = original._aliases,
+ _fail = original._fail,
+ _allowAsync = original._allowAsync,
+ _allowUnawaited = original._allowUnawaited,
+ // Never read from an aliased context because they are never present in
+ // `_clauses`.
+ _label = _emptyLabel;
+
+ /// Create a context nested under [parent].
+ ///
+ /// The [_label] callback should not return an empty iterable.
+ _TestContext._child(this._value, this._label, _TestContext<dynamic> parent)
+ : _parent = parent,
+ _fail = parent._fail,
+ _allowAsync = parent._allowAsync,
+ _allowUnawaited = parent._allowUnawaited,
+ _clauses = [],
+ _aliases = [];
+
+ @override
+ void expect(
+ Iterable<String> Function() clause, Rejection? Function(T) predicate) {
+ _clauses.add(_ExpectationClause(clause));
+ final rejection =
+ _value.apply((actual) => predicate(actual)?._fillActual(actual));
+ if (rejection != null) {
+ _fail(_failure(rejection));
+ }
+ }
+
+ @override
+ Future<void> expectAsync(Iterable<String> Function() clause,
+ FutureOr<Rejection?> Function(T) predicate) async {
+ if (!_allowAsync) {
+ throw StateError(
+ 'Async expectations cannot be used on a synchronous subject');
+ }
+ _clauses.add(_ExpectationClause(clause));
+ final outstandingWork = TestHandle.current.markPending();
+ try {
+ final rejection = await _value.apply(
+ (actual) async => (await predicate(actual))?._fillActual(actual));
+ if (rejection == null) return;
+ _fail(_failure(rejection));
+ } finally {
+ outstandingWork.complete();
+ }
+ }
+
+ @override
+ void expectUnawaited(Iterable<String> Function() clause,
+ void Function(T actual, void Function(Rejection) reject) predicate) {
+ if (!_allowUnawaited) {
+ throw StateError('Late expectations cannot be used for soft checks');
+ }
+ _clauses.add(_ExpectationClause(clause));
+ _value.apply((actual) {
+ predicate(actual, (r) => _fail(_failure(r._fillActual(actual))));
+ });
+ }
+
+ @override
+ Subject<R> nest<R>(
+ Iterable<String> Function() label, Extracted<R> Function(T) extract,
+ {bool atSameLevel = false}) {
+ final result = _value.map((actual) => extract(actual)._fillActual(actual));
+ final rejection = result._rejection;
+ if (rejection != null) {
+ _clauses.add(_ExpectationClause(label));
+ _fail(_failure(rejection));
+ }
+ final value = result._value ?? _Absent<R>();
+ final _TestContext<R> context;
+ if (atSameLevel) {
+ context = _TestContext._alias(this, value);
+ _aliases.add(context);
+ _clauses.add(_ExpectationClause(label));
+ } else {
+ context = _TestContext._child(value, label, this);
+ _clauses.add(context);
+ }
+ return Subject._(context);
+ }
+
+ @override
+ Future<void> nestAsync<R>(
+ Iterable<String> Function() label,
+ FutureOr<Extracted<R>> Function(T) extract,
+ AsyncCondition<R>? nestedCondition) async {
+ if (!_allowAsync) {
+ throw StateError(
+ 'Async expectations cannot be used on a synchronous subject');
+ }
+ final outstandingWork = TestHandle.current.markPending();
+ try {
+ final result = await _value.mapAsync(
+ (actual) async => (await extract(actual))._fillActual(actual));
+ final rejection = result._rejection;
+ if (rejection != null) {
+ _clauses.add(_ExpectationClause(label));
+ _fail(_failure(rejection));
+ }
+ final value = result._value ?? _Absent<R>();
+ final context = _TestContext<R>._child(value, label, this);
+ _clauses.add(context);
+ await nestedCondition?.call(Subject<R>._(context));
+ } finally {
+ outstandingWork.complete();
+ }
+ }
+
+ CheckFailure _failure(Rejection rejection) =>
+ CheckFailure(rejection, () => _root.detail(this));
+
+ _TestContext get _root {
+ _TestContext<dynamic> current = this;
+ while (current._parent != null) {
+ current = current._parent;
+ }
+ return current;
+ }
+
+ @override
+ FailureDetail detail(_TestContext failingContext) {
+ final thisContextFailed =
+ identical(failingContext, this) || _aliases.contains(failingContext);
+ var foundDepth = thisContextFailed ? 0 : -1;
+ var foundOverlap = thisContextFailed ? 0 : -1;
+ var successfulOverlap = 0;
+ final expected = <String>[];
+ if (_clauses.isEmpty) {
+ expected.addAll(_label());
+ } else {
+ expected.addAll(postfixLast(' that:', _label()));
+ for (var clause in _clauses) {
+ final details = clause.detail(failingContext);
+ expected.addAll(indent(details.expected));
+ if (details.depth >= 0) {
+ assert(foundDepth == -1);
+ assert(foundOverlap == -1);
+ foundDepth = details.depth + 1;
+ foundOverlap = details._actualOverlap + successfulOverlap + 1;
+ } else {
+ if (foundDepth == -1) {
+ successfulOverlap += details.expected.length;
+ }
+ }
+ }
+ }
+ return FailureDetail(expected, foundOverlap, foundDepth);
+ }
+}
+
+/// A context which never runs expectations and can never fail.
+final class _SkippedContext<T> implements Context<T> {
+ @override
+ void expect(
+ Iterable<String> Function() clause, Rejection? Function(T) predicate) {
+ // no-op
+ }
+
+ @override
+ Future<void> expectAsync(Iterable<String> Function() clause,
+ FutureOr<Rejection?> Function(T) predicate) async {
+ // no-op
+ }
+
+ @override
+ void expectUnawaited(Iterable<String> Function() clause,
+ void Function(T actual, void Function(Rejection) reject) predicate) {
+ // no-op
+ }
+
+ @override
+ Subject<R> nest<R>(
+ Iterable<String> Function() label, Extracted<R> Function(T p1) extract,
+ {bool atSameLevel = false}) {
+ return Subject._(_SkippedContext());
+ }
+
+ @override
+ Future<void> nestAsync<R>(
+ Iterable<String> Function() label,
+ FutureOr<Extracted<R>> Function(T p1) extract,
+ AsyncCondition<R>? nestedCondition) async {
+ // no-op
+ }
+}
+
+abstract interface class _ClauseDescription {
+ FailureDetail detail(_TestContext failingContext);
+}
+
+class _ExpectationClause implements _ClauseDescription {
+ final Iterable<String> Function() _expected;
+ _ExpectationClause(this._expected);
+ @override
+ FailureDetail detail(_TestContext failingContext) =>
+ FailureDetail(_expected(), -1, -1);
+}
+
+/// The result an expectation that failed for a subject..
+final class CheckFailure {
+ /// The specific rejected value within the overall subject that caused the
+ /// failure.
+ ///
+ /// The [Rejection.actual] may be a property derived from the value at the
+ /// root of the subject, for instance a field or an element in a collection.
+ final Rejection rejection;
+
+ /// The context within the overall subject where an expectation resulted in
+ /// the [rejection].
+ late final FailureDetail detail = _readDetail();
+
+ final FailureDetail Function() _readDetail;
+
+ CheckFailure(this.rejection, this._readDetail);
+}
+
+/// The context for a failed expectation.
+///
+/// A subject may have some number of succeeding expectations, and the failure may
+/// be for an expectation against a property derived from the value at the root
+/// of the subject. For example, in `check([]).length.equals(1)` the
+/// specific value that gets rejected is `0` from the length of the list, and
+/// the subject that sees the rejection is nested with the label "has length".
+final class FailureDetail {
+ /// A description of all the conditions the subject was expected to satisfy.
+ ///
+ /// Each subject has a label. At the root the label is typically "a
+ /// <Type>" and nested subjects get a label based on the condition
+ /// which extracted a property for further checks. Each level of nesting is
+ /// described as "<label> that:" followed by an indented list of the
+ /// expectations for that property.
+ ///
+ /// For example:
+ ///
+ /// a List that:
+ /// has length that:
+ /// equals <3>
+ final Iterable<String> expected;
+
+ /// A description of the conditions the checked value satisfied.
+ ///
+ /// Matches the format of [expected], except it will be cut off after the
+ /// label for the subject that had a failing expectation. For example, if the
+ /// equality check for the length of a list fails:
+ ///
+ /// a List that:
+ /// has length that:
+ ///
+ /// If the subject with a failing expectation is the root, returns an empty
+ /// list. Instead the "Actual: " value from the rejection can be used without
+ /// indentation.
+ Iterable<String> get actual =>
+ _actualOverlap > 0 ? expected.take(_actualOverlap + 1) : const [];
+
+ /// The number of lines from [expected] which describe conditions that were
+ /// successful.
+ ///
+ /// A failed expectation on a derived property may have some number of
+ /// expectations that were checked and satisfied starting from the root
+ /// subject. This field indicates how many lines of expectations were
+ /// successful.
+ final int _actualOverlap;
+
+ /// The number of times the failing subject was nested from the root subject.
+ ///
+ /// Indicates how far the "Actual: " and "Which: " lines from the [Rejection]
+ /// should be indented so that they are at the same level of indentation as
+ /// the label for the subject where the expectation failed.
+ ///
+ /// For example, if a `List` is expected to and have a certain length
+ /// [expected] may be:
+ ///
+ /// a List that:
+ /// has length that:
+ /// equals <3>
+ ///
+ /// If the actual value had an incorrect length, the [depth] will be `1` to
+ /// indicate that the failure occurred checking one of the expectations
+ /// against the `has length` label.
+ final int depth;
+
+ FailureDetail(this.expected, this._actualOverlap, this.depth);
+}
+
+/// A description of a value that failed an expectation.
+final class Rejection {
+ /// A description of the actual value as it relates to the expectation.
+ ///
+ /// This may use [literal] to show a String representation of the value, or it
+ /// may be a description of a specific aspect of the value. For instance an
+ /// expectation that a Future completes to a value may describe the actual as
+ /// "A Future that completes to an error".
+ ///
+ /// When a value is rejected with no [actual] argument, it will be filled in
+ /// with the [literal] representation of the value.
+ ///
+ /// Lines should be split to separate elements, and individual strings should
+ /// not contain newlines.
+ ///
+ /// This is printed following an "Actual: " label in the output of a failure
+ /// message. All lines in the message will be indented to the level of the
+ /// expectation in the description, and printed following the descriptions of
+ /// any expectations that have already passed.
+ final Iterable<String> actual;
+
+ /// A description of the way that [actual] failed to meet the expectation.
+ ///
+ /// An expectation can provide extra detail, or focus attention on a specific
+ /// part of the value. For instance when comparing multiple elements in a
+ /// collection, the rejection may describe that the value "has an unequal
+ /// value at index 3".
+ ///
+ /// Lines should be separate values in the iterable, if any element contains a
+ /// newline it may cause problems with indentation in the output.
+ ///
+ /// When provided, this is printed following a "Which: " label at the end of
+ /// the output for the failure message.
+ final Iterable<String>? which;
+
+ Rejection _fillActual(Object? value) => actual.isNotEmpty
+ ? this
+ : Rejection(actual: literal(value), which: which);
+
+ Rejection({this.actual = const [], this.which});
+}
diff --git a/pkgs/checks/lib/src/collection_equality.dart b/pkgs/checks/lib/src/collection_equality.dart
new file mode 100644
index 0000000..da6064a
--- /dev/null
+++ b/pkgs/checks/lib/src/collection_equality.dart
@@ -0,0 +1,431 @@
+// 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:collection';
+
+import '../context.dart';
+
+/// Returns a descriptive `which` for a rejection if the elements of [actual]
+/// are unequal to the elements of [expected].
+///
+/// {@template deep_collection_equals}
+/// Elements, keys, or values within [expected] which are a collections are
+/// deeply compared for equality with a collection in the same position within
+/// [actual]. Elements which are collection types are not compared with the
+/// native identity based equality or custom equality operator overrides.
+///
+/// Elements, keys, or values within [expected] which are [Condition] callbacks
+/// are run against the value in the same position within [actual].
+/// Condition callbacks must take a `Subject<Object?>` or `Subject<dynamic>` and
+/// may not use a more specific generic.
+/// Use `(Subject<Object?> s) => s.isA<Type>()` to check expectations for
+/// specific element types.
+/// Note also that the argument type `Subject<Object?>` cannot be inferred and
+/// must be explicit in the function definition.
+///
+/// Elements and values within [expected] which are any other type are compared
+/// using `operator ==` equality.
+///
+/// Deep equality checks for [Map] instances depend on the structure of the
+/// expected map.
+/// If all keys of the expectation are non-collection and non-condition values
+/// the keys are compared through the map instances with [Map.containsKey].
+///
+/// If any key in the expectation is a `Condition` or a collection type the map
+/// will be compared as a [Set] of entries where both the key and value must
+/// match.
+///
+/// Comparing sets or maps with complex keys has a runtime which is polynomial
+/// on the the size of those collections.
+/// These comparisons do not use [Set.contains] or [Map.containsKey],
+/// and there will not be runtime benefits from hashing.
+/// Custom collection behavior of `contains` is ignored.
+/// For example, it is not possible to distinguish between a `Set` and a
+/// `Set.identity`.
+///
+/// Collections may be nested to a maximum depth of 1000. Recursive collections
+/// are not allowed.
+/// {@endtemplate}
+Iterable<String>? deepCollectionEquals(Object actual, Object expected) {
+ try {
+ return _deepCollectionEquals(actual, expected, 0);
+ } on _ExceededDepthError {
+ return ['exceeds the depth limit of $_maxDepth'];
+ }
+}
+
+const _maxDepth = 1000;
+
+class _ExceededDepthError extends Error {}
+
+Iterable<String>? _deepCollectionEquals(
+ Object actual, Object expected, int depth) {
+ assert(actual is Iterable || actual is Map);
+ assert(expected is Iterable || expected is Map);
+
+ final queue = Queue.of([_Search(_Path.root(), actual, expected, depth)]);
+ while (queue.isNotEmpty) {
+ final toCheck = queue.removeFirst();
+ final currentActual = toCheck.actual;
+ final currentExpected = toCheck.expected;
+ final path = toCheck.path;
+ final currentDepth = toCheck.depth;
+ Iterable<String>? rejectionWhich;
+ if (currentExpected is Set) {
+ rejectionWhich = _findSetDifference(
+ currentActual, currentExpected, path, currentDepth);
+ } else if (currentExpected is Iterable) {
+ rejectionWhich = _findIterableDifference(
+ currentActual, currentExpected, path, queue, currentDepth);
+ } else {
+ currentExpected as Map;
+ rejectionWhich = _findMapDifference(
+ currentActual, currentExpected, path, queue, currentDepth);
+ }
+ if (rejectionWhich != null) return rejectionWhich;
+ }
+ return null;
+}
+
+List<String>? _findIterableDifference(Object? actual,
+ Iterable<Object?> expected, _Path path, Queue<_Search> queue, int depth) {
+ if (actual is! Iterable) {
+ return ['${path}is not an Iterable'];
+ }
+ var actualIterator = actual.iterator;
+ var expectedIterator = expected.iterator;
+ for (var index = 0;; index++) {
+ var actualNext = actualIterator.moveNext();
+ var expectedNext = expectedIterator.moveNext();
+ if (!expectedNext && !actualNext) break;
+ if (!expectedNext) {
+ return [
+ '${path}has more elements than expected',
+ 'expected an iterable with $index element(s)'
+ ];
+ }
+ if (!actualNext) {
+ return [
+ '${path}has too few elements',
+ 'expected an iterable with at least ${index + 1} element(s)'
+ ];
+ }
+ final difference = _compareValue(actualIterator.current,
+ expectedIterator.current, path, index, queue, depth);
+ if (difference != null) return difference;
+ }
+ return null;
+}
+
+List<String>? _compareValue(Object? actualValue, Object? expectedValue,
+ _Path path, Object? pathAppend, Queue<_Search> queue, int depth) {
+ if (expectedValue is Iterable || expectedValue is Map) {
+ if (depth + 1 > _maxDepth) throw _ExceededDepthError();
+ queue.addLast(_Search(
+ path.append(pathAppend), actualValue, expectedValue, depth + 1));
+ } else if (expectedValue is Condition) {
+ final failure = softCheck(actualValue, expectedValue);
+ if (failure != null) {
+ final which = failure.rejection.which;
+ return [
+ 'has an element ${path.append(pathAppend)}that:',
+ ...indent(failure.detail.actual.skip(1)),
+ ...indent(prefixFirst('Actual: ', failure.rejection.actual),
+ failure.detail.depth + 1),
+ if (which != null)
+ ...indent(prefixFirst('which ', which), failure.detail.depth + 1)
+ ];
+ }
+ } else {
+ if (actualValue != expectedValue) {
+ return [
+ ...prefixFirst('${path.append(pathAppend)}is ', literal(actualValue)),
+ ...prefixFirst('which does not equal ', literal(expectedValue))
+ ];
+ }
+ }
+ return null;
+}
+
+bool _elementMatches(Object? actual, Object? expected, int depth) {
+ if (expected == null) return actual == null;
+ if (expected is Iterable || expected is Map) {
+ if (++depth > _maxDepth) throw _ExceededDepthError();
+ return actual != null &&
+ _deepCollectionEquals(actual, expected, depth) == null;
+ }
+ if (expected is Condition) {
+ return softCheck(actual, expected) == null;
+ }
+ return expected == actual;
+}
+
+Iterable<String>? _findSetDifference(
+ Object? actual, Set<Object?> expected, _Path path, int depth) {
+ if (actual is! Set) {
+ return ['${path}is not a Set'];
+ }
+ return unorderedCompare(
+ actual,
+ expected,
+ (actual, expected) => _elementMatches(actual, expected, depth),
+ (expected, _, count) => [
+ ...prefixFirst('${path}has no element to match ', literal(expected)),
+ if (count > 1) 'or ${count - 1} other elements',
+ ],
+ (actual, _, count) => [
+ ...prefixFirst('${path}has an unexpected element ', literal(actual)),
+ if (count > 1) 'and ${count - 1} other unexpected elements',
+ ],
+ );
+}
+
+Iterable<String>? _findMapDifference(
+ Object? actual,
+ Map<Object?, Object?> expected,
+ _Path path,
+ Queue<_Search> queue,
+ int depth) {
+ if (actual is! Map) {
+ return ['${path}is not a Map'];
+ }
+ if (expected.keys
+ .any((key) => key is Condition || key is Iterable || key is Map)) {
+ return _findAmbiguousMapDifference(actual, expected, path, depth);
+ } else {
+ return _findUnambiguousMapDifference(actual, expected, path, queue, depth);
+ }
+}
+
+Iterable<String> _describeEntry(MapEntry<Object?, Object?> entry) {
+ final key = literal(entry.key);
+ final value = literal(entry.value);
+ return [
+ ...key.take(key.length - 1),
+ '${key.last}: ${value.first}',
+ ...value.skip(1)
+ ];
+}
+
+/// Returns a description of a difference found between [actual] and [expected]
+/// when [expected] has only direct key values and there is a 1:1 mapping
+/// between an expected value and a checked value in the map.
+Iterable<String>? _findUnambiguousMapDifference(
+ Map<Object?, Object?> actual,
+ Map<Object?, Object?> expected,
+ _Path path,
+ Queue<_Search> queue,
+ int depth) {
+ for (final entry in expected.entries) {
+ assert(entry.key is! Condition);
+ assert(entry.key is! Iterable);
+ assert(entry.key is! Map);
+ if (!actual.containsKey(entry.key)) {
+ return prefixFirst(
+ '${path}has no key matching expected entry ', _describeEntry(entry));
+ }
+ final difference = _compareValue(
+ actual[entry.key], entry.value, path, entry.key, queue, depth);
+ if (difference != null) return difference;
+ }
+ for (final entry in actual.entries) {
+ if (!expected.containsKey(entry.key)) {
+ return prefixFirst(
+ '${path}has an unexpected key for entry ', _describeEntry(entry));
+ }
+ }
+ return null;
+}
+
+Iterable<String>? _findAmbiguousMapDifference(Map<Object?, Object?> actual,
+ Map<Object?, Object?> expected, _Path path, int depth) =>
+ unorderedCompare(
+ actual.entries,
+ expected.entries,
+ (actual, expected) =>
+ _elementMatches(actual.key, expected.key, depth) &&
+ _elementMatches(actual.value, expected.value, depth),
+ (expectedEntry, _, count) => [
+ ...prefixFirst(
+ '${path}has no entry to match ', _describeEntry(expectedEntry)),
+ if (count > 1) 'or ${count - 1} other entries',
+ ],
+ (actualEntry, _, count) => [
+ ...prefixFirst(
+ '${path}has unexpected entry ', _describeEntry(actualEntry)),
+ if (count > 1) 'and ${count - 1} other unexpected entries',
+ ],
+ );
+
+class _Path {
+ final _Path? parent;
+ final Object? index;
+ _Path._(this.parent, this.index);
+ _Path.root()
+ : parent = null,
+ index = '';
+ _Path append(Object? index) => _Path._(this, index);
+
+ @override
+ String toString() {
+ if (parent == null && index == '') return '';
+ final stack = Queue.of([this]);
+ var current = parent;
+ while (current?.parent != null) {
+ stack.addLast(current!);
+ current = current.parent;
+ }
+ final result = StringBuffer('at ');
+ while (stack.isNotEmpty) {
+ result.write('[');
+ result.write(literal(stack.removeLast().index).join(r'\n'));
+ result.write(']');
+ }
+ result.write(' ');
+ return result.toString();
+ }
+}
+
+class _Search {
+ final _Path path;
+ final Object? actual;
+ final Object? expected;
+ final int depth;
+ _Search(this.path, this.actual, this.expected, this.depth);
+}
+
+/// Returns the `which` for a Rejection if there is no pairing between the
+/// elements of [actual] and [expected] using [elementsEqual].
+///
+/// If there are unmatched expected elements - either actual was too short, or
+/// has mismatched elements - returns a rejection reason from calling
+/// [unmatchedExpected] with an expected value that could not be paired, it's
+/// index, and the count of unmatched elements.
+///
+/// Otherwise, if there are unmatched actual elements - actual was too long -
+/// returns a rejection reason from calling [unmatchedActual] with an actual
+/// value that could not be paired, it's index, and the count of unmatched
+/// elements.
+///
+/// Runtime is at least `O(|actual||expected|)`, and for collections with many
+/// elements which compare as equal the runtime can reach
+/// `O((|actual| + |expected|)^2.5)`.
+Iterable<String>? unorderedCompare<T, E>(
+ Iterable<T> actual,
+ Iterable<E> expected,
+ bool Function(T, E) elementsEqual,
+ Iterable<String> Function(E, int index, int count) unmatchedExpected,
+ Iterable<String> Function(T, int index, int count) unmatchedActual) {
+ final indexedExpected = expected.toList();
+ final indexedActual = actual.toList();
+ final adjacency = <List<int>>[];
+ for (var i = 0; i < indexedExpected.length; i++) {
+ final expectedElement = indexedExpected[i];
+ final pairs = [
+ for (var j = 0; j < indexedActual.length; j++)
+ if (elementsEqual(indexedActual[j], expectedElement)) j
+ ];
+ adjacency.add(pairs);
+ }
+ final unpaired = _findUnpaired(adjacency, indexedActual.length);
+ if (unpaired.first.isNotEmpty) {
+ final firstUnmatched = indexedExpected[unpaired.first.first];
+ return unmatchedExpected(
+ firstUnmatched, unpaired.first.first, unpaired.first.length);
+ }
+ if (unpaired.last.isNotEmpty) {
+ final firstUnmatched = indexedActual[unpaired.last.first];
+ return unmatchedActual(
+ firstUnmatched, unpaired.last.first, unpaired.last.length);
+ }
+ return null;
+}
+
+/// Returns the indices which are unmatched in an optimal pairing in the
+/// bipartite graph represented by [adjacency].
+///
+/// Vertices are represented as integers. The two sets of vertices (`U` and `V`)
+/// in the biparte graph are represented as:
+/// - `U` - the indices of [adjacency].
+/// - `V` - values smaller than [rightVertexCount].
+///
+/// An edge from `U[n]` to `V[m]` is represented by the value `m` being present
+/// in the list at index `n`.
+/// The largest value within any list in [adjacency] must be smaller than
+/// [rightVertexCount].
+///
+/// Returns a List with two values, the unpaired values of `U` and `V` in the
+/// maximum-caridnality matching betweeen them.
+///
+/// If there is a perfect pairing, the returned lists will both be empty.
+///
+/// Uses the Hopcroft–Karp algorithm based on pseudocode from
+/// https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm
+List<List<int>> _findUnpaired(List<List<int>> adjacency, int rightVertexCount) {
+ final leftLength = adjacency.length;
+ final rightLength = rightVertexCount;
+ // The last index represents a "dummy vertex"
+ final distances = List<num>.filled(leftLength + 1, double.infinity);
+ // Initially everything is paired with the "dummy vertex" of the opposite set
+ final leftPairs = List.filled(leftLength, rightLength);
+ final rightPairs = List.filled(rightLength, leftLength);
+
+ bool bfs() {
+ final queue = Queue<int>();
+ for (var leftIndex = 0; leftIndex < leftLength; leftIndex++) {
+ if (leftPairs[leftIndex] == rightLength) {
+ distances[leftIndex] = 0;
+ queue.add(leftIndex);
+ } else {
+ distances[leftIndex] = double.infinity;
+ }
+ }
+ distances.last = double.infinity;
+ while (queue.isNotEmpty) {
+ final current = queue.removeFirst();
+ if (distances[current] < distances[leftLength]) {
+ for (final rightIndex in adjacency[current]) {
+ if (distances[rightPairs[rightIndex]].isInfinite) {
+ distances[rightPairs[rightIndex]] = distances[current] + 1;
+ queue.addLast(rightPairs[rightIndex]);
+ }
+ }
+ }
+ }
+ return !distances.last.isInfinite;
+ }
+
+ bool dfs(int leftIndex) {
+ if (leftIndex == leftLength) return true;
+ for (final rightIndex in adjacency[leftIndex]) {
+ if (distances[rightPairs[rightIndex]] == distances[leftIndex] + 1) {
+ if (dfs(rightPairs[rightIndex])) {
+ leftPairs[leftIndex] = rightIndex;
+ rightPairs[rightIndex] = leftIndex;
+ return true;
+ }
+ }
+ }
+ distances[leftIndex] = double.infinity;
+ return false;
+ }
+
+ while (bfs()) {
+ for (var leftIndex = 0; leftIndex < leftLength; leftIndex++) {
+ if (leftPairs[leftIndex] == rightLength) {
+ dfs(leftIndex);
+ }
+ }
+ }
+ return [
+ [
+ for (int i = 0; i < leftLength; i++)
+ if (leftPairs[i] == rightLength) i
+ ],
+ [
+ for (int i = 0; i < rightLength; i++)
+ if (rightPairs[i] == leftLength) i
+ ]
+ ];
+}
diff --git a/pkgs/checks/lib/src/describe.dart b/pkgs/checks/lib/src/describe.dart
new file mode 100644
index 0000000..f05a164
--- /dev/null
+++ b/pkgs/checks/lib/src/describe.dart
@@ -0,0 +1,163 @@
+// Copyright (c) 2022, 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:convert';
+
+import 'checks.dart' show Condition, describe;
+
+/// Returns a pretty-printed representation of [object].
+///
+/// When possible, lines will be kept under [_maxLineLength]. This isn't
+/// guaranteed, since individual objects may have string representations that
+/// are too long, but most lines will be less than [_maxLineLength] long.
+///
+/// [Iterable]s and [Map]s will only print their first [_maxItems] elements or
+/// key/value pairs, respectively.
+Iterable<String> literal(Object? object) => _prettyPrint(object, 0, {}, true);
+
+const _maxLineLength = 80;
+const _maxItems = 25;
+
+Iterable<String> _prettyPrint(
+ Object? object, int indentSize, Set<Object?> seen, bool isTopLevel) {
+ if (seen.contains(object)) return ['(recursive)'];
+ seen = seen.union({object});
+ Iterable<String> prettyPrintNested(Object? child) =>
+ _prettyPrint(child, indentSize + 2, seen, false);
+
+ if (object is Iterable) {
+ String open, close;
+ if (object is List) {
+ open = '[';
+ close = ']';
+ } else if (object is Set) {
+ open = '{';
+ close = '}';
+ } else {
+ open = '(';
+ close = ')';
+ }
+ final elements = object.map(prettyPrintNested).toList();
+ return _prettyPrintCollection(
+ open, close, elements, _maxLineLength - indentSize);
+ } else if (object is Map) {
+ final entries = object.entries.map((entry) {
+ final key = prettyPrintNested(entry.key);
+ final value = prettyPrintNested(entry.value);
+ return [
+ ...key.take(key.length - 1),
+ '${key.last}: ${value.first}',
+ ...value.skip(1)
+ ];
+ }).toList();
+ return _prettyPrintCollection(
+ '{', '}', entries, _maxLineLength - indentSize);
+ } else if (object is String) {
+ if (object.isEmpty) return ["''"];
+ final escaped = const LineSplitter()
+ .convert(object)
+ .map(escape)
+ .map((line) => line.replaceAll("'", r"\'"))
+ .toList();
+ return prefixFirst("'", postfixLast("'", escaped));
+ } else if (object is Condition<Never>) {
+ return ['<A value that:', ...postfixLast('>', describe(object))];
+ } else {
+ final value = const LineSplitter().convert(object.toString());
+ return isTopLevel ? prefixFirst('<', postfixLast('>', value)) : value;
+ }
+}
+
+Iterable<String> _prettyPrintCollection(
+ String open, String close, List<Iterable<String>> elements, int maxLength) {
+ if (elements.length > _maxItems) {
+ elements.replaceRange(_maxItems - 1, elements.length, [
+ ['...']
+ ]);
+ }
+ if (elements.every((e) => e.length == 1)) {
+ final singleLine = '$open${elements.map((e) => e.single).join(', ')}$close';
+ if (singleLine.length <= maxLength) {
+ return [singleLine];
+ }
+ }
+ if (elements.length == 1) {
+ return prefixFirst(open, postfixLast(close, elements.single));
+ }
+ return [
+ ...prefixFirst(open, postfixLast(',', elements.first)),
+ for (var element in elements.skip(1).take(elements.length - 2))
+ ...postfixLast(',', element),
+ ...postfixLast(close, elements.last),
+ ];
+}
+
+Iterable<String> indent(Iterable<String> lines, [int depth = 1]) {
+ final indent = ' ' * depth;
+ return lines.map((line) => '$indent$line');
+}
+
+/// Prepends [prefix] to the first line of [lines].
+///
+/// If [lines] is empty, the result will be as well. The prefix will not be
+/// returned for an empty input.
+Iterable<String> prefixFirst(String prefix, Iterable<String> lines) sync* {
+ var isFirst = true;
+ for (var line in lines) {
+ if (isFirst) {
+ yield '$prefix$line';
+ isFirst = false;
+ } else {
+ yield line;
+ }
+ }
+}
+
+/// Append [postfix] to the last line of [lines].
+///
+/// If [lines] is empty, the result will be as well. The postfix will not be
+/// returned for an empty input.
+Iterable<String> postfixLast(String postfix, Iterable<String> lines) sync* {
+ var iterator = lines.iterator;
+ var hasNext = iterator.moveNext();
+ while (hasNext) {
+ final line = iterator.current;
+ hasNext = iterator.moveNext();
+ yield hasNext ? line : '$line$postfix';
+ }
+}
+
+/// Returns [output] with all whitespace characters represented as their escape
+/// sequences.
+///
+/// Backslash characters are escaped as `\\`
+String escape(String output) {
+ output = output.replaceAll('\\', r'\\');
+ return output.replaceAllMapped(_escapeRegExp, (match) {
+ var mapped = _escapeMap[match[0]];
+ if (mapped != null) return mapped;
+ return _hexLiteral(match[0]!);
+ });
+}
+
+/// A [RegExp] that matches whitespace characters that should be escaped.
+final _escapeRegExp = RegExp(
+ '[\\x00-\\x07\\x0E-\\x1F${_escapeMap.keys.map(_hexLiteral).join()}]');
+
+/// A [Map] between whitespace characters and their escape sequences.
+const _escapeMap = {
+ '\n': r'\n',
+ '\r': r'\r',
+ '\f': r'\f',
+ '\b': r'\b',
+ '\t': r'\t',
+ '\v': r'\v',
+ '\x7F': r'\x7F', // delete
+};
+
+/// Given single-character string, return the hex-escaped equivalent.
+String _hexLiteral(String input) {
+ var rune = input.runes.single;
+ return r'\x' + rune.toRadixString(16).toUpperCase().padLeft(2, '0');
+}
diff --git a/pkgs/checks/lib/src/extensions/async.dart b/pkgs/checks/lib/src/extensions/async.dart
new file mode 100644
index 0000000..9d9e85a
--- /dev/null
+++ b/pkgs/checks/lib/src/extensions/async.dart
@@ -0,0 +1,476 @@
+// Copyright (c) 2022, 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 'package:async/async.dart';
+
+import '../../context.dart';
+
+extension FutureChecks<T> on Subject<Future<T>> {
+ /// Expects that the `Future` completes to a value without throwing.
+ ///
+ /// Fails if the future completes as an error.
+ ///
+ /// Pass [completionCondition] to check expectations on the completion result.
+ ///
+ /// The returned future will complete when the subject future has completed,
+ /// and [completionCondition] has optionally been checked.
+ Future<void> completes([AsyncCondition<T>? completionCondition]) async {
+ await context.nestAsync<T>(() => ['completes to a value'], (actual) async {
+ try {
+ return Extracted.value(await actual);
+ } catch (e, st) {
+ return Extracted.rejection(actual: [
+ 'a future that completes as an error'
+ ], which: [
+ ...prefixFirst('threw ', postfixLast(' at:', literal(e))),
+ ...const LineSplitter().convert(st.toString())
+ ]);
+ }
+ }, completionCondition);
+ }
+
+ /// Expects that the `Future` never completes as a value or an error.
+ ///
+ /// Immediately returns and does not cause the test to remain running if it
+ /// ends.
+ /// If the future completes at any time, raises a test failure. This may
+ /// happen after the test has already appeared to succeed.
+ ///
+ /// Not compatible with [softCheck] or [softCheckAsync] since there is no
+ /// concrete end point where this condition has definitely succeeded.
+ void doesNotComplete() {
+ context.expectUnawaited(() => ['does not complete'], (actual, reject) {
+ unawaited(actual.then((r) {
+ reject(Rejection(
+ actual: prefixFirst('a future that completed to ', literal(r))));
+ }, onError: (Object e, StackTrace st) {
+ reject(Rejection(actual: [
+ 'a future that completed as an error:'
+ ], which: [
+ ...prefixFirst('threw ', literal(e)),
+ ...const LineSplitter().convert(st.toString())
+ ]));
+ }));
+ });
+ }
+
+ /// Expects that the `Future` completes as an error.
+ ///
+ /// Fails if the future completes to a value.
+ ///
+ /// Pass [errorCondition] to check expectations on the error thrown by the
+ /// future.
+ ///
+ /// The returned future will complete when the subject future has completed,
+ /// and [errorCondition] has optionally been checked.
+ Future<void> throws<E extends Object>(
+ [AsyncCondition<E>? errorCondition]) async {
+ await context.nestAsync<E>(
+ () => ['completes to an error${E == Object ? '' : ' of type $E'}'],
+ (actual) async {
+ try {
+ return Extracted.rejection(
+ actual: prefixFirst('completed to ', literal(await actual)),
+ which: ['did not throw']);
+ } on E catch (e) {
+ return Extracted.value(e);
+ } catch (e, st) {
+ return Extracted.rejection(
+ actual: prefixFirst('completed to error ', literal(e)),
+ which: [
+ 'threw an exception that is not a $E at:',
+ ...const LineSplitter().convert(st.toString())
+ ]);
+ }
+ }, errorCondition);
+ }
+}
+
+/// Expectations on a [StreamQueue].
+///
+/// Streams should be wrapped in user test code so that any reuse of the same
+/// Stream, and the full stream lifecycle, is explicit.
+extension StreamChecks<T> on Subject<StreamQueue<T>> {
+ /// Calls [Context.expectAsync] and wraps [predicate] with a transaction.
+ ///
+ /// The transaction is committed if the check passes, or rejected if it fails.
+ Future<void> _expectAsync(Iterable<String> Function() clause,
+ FutureOr<Rejection?> Function(StreamQueue<T>) predicate) =>
+ context.expectAsync(clause, (actual) async {
+ final transaction = actual.startTransaction();
+ final copy = transaction.newQueue();
+ final result = await predicate(copy);
+ if (result == null) {
+ transaction.commit(copy);
+ } else {
+ transaction.reject();
+ }
+ return result;
+ });
+
+ /// Expect that the `Stream` emits a value without first emitting an error.
+ ///
+ /// Fails if the stream emits an error instead of a value, or closes without
+ /// emitting a value.
+ ///
+ /// If an error is emitted the queue will be left in its original state, the
+ /// error will not be consumed.
+ /// If an event is emitted, it will be consumed from the queue.
+ ///
+ /// Pass [emittedCondition] to check expectations on the value emitted by the
+ /// stream.
+ ///
+ /// The returned future will complete when the stream has emitted, errored, or
+ /// ended, and the [emittedCondition] has optionally been checked.
+ Future<void> emits([AsyncCondition<T>? emittedCondition]) async {
+ await context.nestAsync<T>(() => ['emits a value'], (actual) async {
+ if (!await actual.hasNext) {
+ return Extracted.rejection(
+ actual: ['a stream'],
+ which: ['closed without emitting enough values']);
+ }
+ try {
+ await actual.peek;
+ return Extracted.value(await actual.next);
+ } catch (e, st) {
+ return Extracted.rejection(
+ actual: prefixFirst('a stream with error ', literal(e)),
+ which: [
+ 'emitted an error instead of a value at:',
+ ...const LineSplitter().convert(st.toString())
+ ]);
+ }
+ }, emittedCondition);
+ }
+
+ /// Expects that the stream emits an error of type [E].
+ ///
+ /// Fails if the stream emits any value.
+ /// Fails if the stream emits an error with an incorrect type.
+ /// Fails if the stream closes without emitting an error.
+ ///
+ /// If an event is emitted the queue will be left in its original state, the
+ /// event will not be consumed.
+ /// If an error is emitted, it will be consumed from the queue.
+ ///
+ /// Pass [errorCondition] to check expectations on the error emitted by the
+ /// stream.
+ ///
+ /// The returned future will complete when the stream has emitted, errored, or
+ /// ended, and the [errorCondition] has optionally been checked.
+ Future<void> emitsError<E extends Object>(
+ [AsyncCondition<E>? errorCondition]) async {
+ await context.nestAsync<E>(
+ () => ['emits an error${E == Object ? '' : ' of type $E'}'],
+ (actual) async {
+ if (!await actual.hasNext) {
+ return Extracted.rejection(
+ actual: ['a stream'],
+ which: ['closed without emitting an expected error']);
+ }
+ try {
+ final value = await actual.peek;
+ return Extracted.rejection(
+ actual: prefixFirst('a stream emitting value ', literal(value)),
+ which: ['closed without emitting an error']);
+ } on E catch (e) {
+ await actual.next.then<void>((_) {}, onError: (_) {});
+ return Extracted.value(e);
+ } catch (e, st) {
+ return Extracted.rejection(
+ actual: prefixFirst('a stream with error ', literal(e)),
+ which: [
+ 'emitted an error which is not $E at:',
+ ...const LineSplitter().convert(st.toString())
+ ]);
+ }
+ }, errorCondition);
+ }
+
+ /// Expects that the `Stream` emits any number of events before emitting an
+ /// event that satisfies [condition].
+ ///
+ /// Returns a `Future` that completes after the stream has emitted an event
+ /// that satisfies [condition].
+ ///
+ /// Fails if the stream emits an error or closes before emitting a matching
+ /// event.
+ ///
+ /// If this expectation fails, the source queue will be left in its original
+ /// state.
+ /// If this expectation succeeds, consumes the matching event and all prior
+ /// events.
+ Future<void> emitsThrough(AsyncCondition<T> condition) async {
+ await _expectAsync(
+ () => [
+ 'emits any values then emits a value that:',
+ ...describe(condition)
+ ], (actual) async {
+ var count = 0;
+ while (await actual.hasNext) {
+ if (softCheck(await actual.next, condition) == null) {
+ return null;
+ }
+ count++;
+ }
+ return Rejection(
+ actual: ['a stream'],
+ which: ['ended after emitting $count elements with none matching']);
+ });
+ }
+
+ /// Expects that the stream satisfies each condition in [conditions] serially.
+ ///
+ /// Waits for each condition to be satisfied or rejected before checking the
+ /// next. Subsequent conditions will not see any events consumed by earlier
+ /// conditions.
+ ///
+ /// ```dart
+ /// await check(someStream).withQueue.inOrder([
+ /// (s) => s.emits((e) => e.equals(0)),
+ /// (s) => s.emits((e) => e.equals(1)),
+ /// ]);
+ /// ```
+ ///
+ /// If this expectation fails, the source queue will be left in its original
+ /// state.
+ /// If this expectation succeeds, consumes as many events from the source
+ /// stream as are consumed by all the conditions.
+ Future<void> inOrder(
+ Iterable<AsyncCondition<StreamQueue<T>>> conditions) async {
+ conditions = conditions.toList();
+ final descriptions = <String>[];
+ await _expectAsync(
+ () => descriptions.isEmpty
+ ? ['satisfies ${conditions.length} conditions in order']
+ : descriptions, (actual) async {
+ var satisfiedCount = 0;
+ for (var condition in conditions) {
+ descriptions.addAll(await describeAsync(condition));
+ final failure = await softCheckAsync(actual, condition);
+ if (failure != null) {
+ final which = failure.rejection.which;
+ return Rejection(actual: [
+ 'a stream'
+ ], which: [
+ if (satisfiedCount > 0) 'satisfied $satisfiedCount conditions then',
+ 'failed to satisfy the condition at index $satisfiedCount',
+ if (failure.detail.depth > 0) ...[
+ 'because it:',
+ ...indent(
+ failure.detail.actual.skip(1), failure.detail.depth - 1),
+ ...indent(prefixFirst('Actual: ', failure.rejection.actual),
+ failure.detail.depth),
+ if (which != null)
+ ...indent(prefixFirst('Which: ', which), failure.detail.depth),
+ ] else ...[
+ if (which != null) ...prefixFirst('because it ', which),
+ ],
+ ]);
+ }
+ satisfiedCount++;
+ }
+ return null;
+ });
+ }
+
+ /// Expects that the stream statisfies at least one condition from
+ /// [conditions].
+ ///
+ /// If this expectation fails, the source queue will be left in its original
+ /// state.
+ /// If this expectation succeeds, consumes the same events from the source
+ /// queue as the satisfied condition. If multiple conditions are satisfied,
+ /// chooses the condition which consumed the most events.
+ Future<void> anyOf(
+ Iterable<AsyncCondition<StreamQueue<T>>> conditions) async {
+ conditions = conditions.toList();
+ if (conditions.isEmpty) {
+ throw ArgumentError('conditions may not be empty');
+ }
+ final descriptions = <Iterable<String>>[];
+ await context.expectAsync(
+ () => descriptions.isEmpty
+ ? ['satisfies any of ${conditions.length} conditions']
+ : [
+ 'satisfies one of:',
+ for (var i = 0; i < descriptions.length; i++) ...[
+ ...descriptions[i],
+ if (i < descriptions.length - 1) 'or,'
+ ]
+ ], (actual) async {
+ final transaction = actual.startTransaction();
+ StreamQueue<T>? longestAccepted;
+ final descriptionFuture = Future.wait(conditions.map(describeAsync));
+ final failures = await Future.wait(conditions.map((condition) async {
+ final copy = transaction.newQueue();
+ final failure = await softCheckAsync(copy, condition);
+ if (failure == null &&
+ (longestAccepted == null ||
+ copy.eventsDispatched > longestAccepted!.eventsDispatched)) {
+ longestAccepted = copy;
+ }
+ return failure;
+ }));
+ descriptions.addAll(await descriptionFuture);
+ if (longestAccepted != null) {
+ transaction.commit(longestAccepted!);
+ return null;
+ }
+ transaction.reject();
+ Iterable<String> failureDetails(int index, CheckFailure? failure) {
+ final actual = failure!.rejection.actual;
+ final which = failure.rejection.which;
+ final detail = failure.detail;
+ final failed = 'failed the condition at index $index';
+ if (detail.depth > 0) {
+ return [
+ '$failed because it:',
+ ...indent(detail.actual.skip(1), detail.depth - 1),
+ ...indent(prefixFirst('Actual: ', actual), detail.depth),
+ if (which != null)
+ ...indent(prefixFirst('Which: ', which), detail.depth),
+ ];
+ } else {
+ return [
+ if (which == null)
+ failed
+ else ...[
+ '$failed because it:',
+ ...indent(which),
+ ],
+ ];
+ }
+ }
+
+ return Rejection(actual: [
+ 'a stream'
+ ], which: [
+ 'failed to satisfy any condition',
+ for (var i = 0; i < failures.length; i++)
+ ...failureDetails(i, failures[i]),
+ ]);
+ });
+ }
+
+ /// Expects that the stream closes without emitting any event that satisfies
+ /// [condition].
+ ///
+ /// Returns a `Future` that completes after the stream has closed.
+ ///
+ /// Fails if the stream emits any even that satisfies [condition].
+ ///
+ /// If this expectation fails, the source queue will be left in its original
+ /// state.
+ /// If this expectation succeeds, consumes all the events that did not satisfy
+ /// [condition] until the end of the stream.
+ Future<void> neverEmits(AsyncCondition<T> condition) async {
+ await _expectAsync(
+ () => ['never emits a value that:', ...describe(condition)],
+ (actual) async {
+ var count = 0;
+ await for (var emitted in actual.rest) {
+ if (softCheck(emitted, condition) == null) {
+ return Rejection(actual: [
+ 'a stream'
+ ], which: [
+ ...prefixFirst('emitted ', literal(emitted)),
+ if (count > 0) 'following $count other items'
+ ]);
+ }
+ count++;
+ }
+ return null;
+ });
+ }
+
+ /// Optionally consumes an event that matches [condition] from the stream.
+ ///
+ /// This expectation never fails.
+ ///
+ /// If a non-matching event is emitted, no events are consumed.
+ /// If a matching event is emitted, that event is consumed.
+ Future<void> mayEmit(AsyncCondition<T> condition) async {
+ await context
+ .expectAsync(() => ['may emit a value that:', ...describe(condition)],
+ (actual) async {
+ if (!await actual.hasNext) return null;
+ try {
+ final value = await actual.peek;
+ if (softCheck(value, condition) == null) {
+ await actual.next;
+ }
+ } catch (_) {
+ // Ignore an emitted error - it does not match he event.
+ }
+ return null;
+ });
+ }
+
+ /// Optionally consumes events that match [condition] from the stream.
+ ///
+ /// This expectation never fails.
+ ///
+ /// Consumes matching events until one of the following happens:
+ /// - A non-matching event is emitted.
+ /// - An error is emitted.
+ /// - The stream closes.
+ Future<void> mayEmitMultiple(AsyncCondition<T> condition) async {
+ await context
+ .expectAsync(() => ['may emit a value that:', ...describe(condition)],
+ (actual) async {
+ while (await actual.hasNext) {
+ try {
+ final value = await actual.peek;
+ if (softCheck(value, condition) == null) {
+ await actual.next;
+ } else {
+ return null;
+ }
+ } catch (_) {
+ return null;
+ }
+ }
+ return null;
+ });
+ }
+
+ /// Expects that the stream closes without emitting any events or errors.
+ ///
+ /// If this expectation fails, the source queue will be left in its original
+ /// state, the event or error that caused it to fail will not be consumed.
+ Future<void> isDone() async {
+ await _expectAsync(() => ['is done'], (actual) async {
+ if (!await actual.hasNext) return null;
+ try {
+ return Rejection(
+ actual: ['a stream'],
+ which: prefixFirst(
+ 'emitted an unexpected value: ', literal(await actual.next)));
+ } catch (e, st) {
+ return Rejection(actual: [
+ 'a stream'
+ ], which: [
+ ...prefixFirst('emitted an unexpected error: ', literal(e)),
+ ...const LineSplitter().convert(st.toString())
+ ]);
+ }
+ });
+ }
+}
+
+extension WithQueueExtension<T> on Subject<Stream<T>> {
+ /// Wrap the stream in a [StreamQueue] to allow using checks from
+ /// [StreamChecks].
+ ///
+ /// Stream expectations operate on a queue, instead of directly on the stream,
+ /// so that they can support conditional expectations and check multiple
+ /// possibilities from the same point in the stream.
+ Subject<StreamQueue<T>> get withQueue =>
+ context.nest(() => [], (actual) => Extracted.value(StreamQueue(actual)),
+ atSameLevel: true);
+}
diff --git a/pkgs/checks/lib/src/extensions/core.dart b/pkgs/checks/lib/src/extensions/core.dart
new file mode 100644
index 0000000..957e175
--- /dev/null
+++ b/pkgs/checks/lib/src/extensions/core.dart
@@ -0,0 +1,184 @@
+// Copyright (c) 2022, 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:convert';
+
+import 'package:meta/meta.dart' as meta;
+
+import '../../context.dart';
+
+extension CoreChecks<T> on Subject<T> {
+ /// Extracts a property of the value for further expectations.
+ ///
+ /// Sets up a clause that the value "has [name] that:" followed by any
+ /// expectations applied to the returned [Subject].
+ @meta.useResult
+ Subject<R> has<R>(R Function(T) extract, String name) {
+ return context.nest(() => ['has $name'], (value) {
+ try {
+ return Extracted.value(extract(value));
+ } catch (e, st) {
+ return Extracted.rejection(which: [
+ ...prefixFirst('threw while trying to read $name: ', literal(e)),
+ ...const LineSplitter().convert(st.toString())
+ ]);
+ }
+ });
+ }
+
+ /// Applies the expectations invoked in [condition] to this subject.
+ ///
+ /// Use this method when it would otherwise not be possible to check multiple
+ /// expectations for this subject due to cascade notation already being used
+ /// in a way that would conflict.
+ ///
+ /// ```
+ /// check(something)
+ /// ..has((s) => s.foo, 'foo').equals(expectedFoo)
+ /// ..has((s) => s.bar, 'bar').which((b) => b
+ /// ..isLessThan(10)
+ /// ..isGreaterThan(0));
+ /// ```
+ void which(Condition<T> condition) => condition(this);
+
+ /// Check that the expectations invoked in [condition] are not satisfied by
+ /// this value.
+ ///
+ /// Asynchronous expectations are not allowed in [condition].
+ void not(Condition<T> condition) {
+ context.expect(
+ () => ['is not a value that:', ...indent(describe(condition))],
+ (actual) {
+ if (softCheck(actual, condition) != null) return null;
+ return Rejection(
+ which: ['is a value that: ', ...indent(describe(condition))],
+ );
+ },
+ );
+ }
+
+ /// Expects that the value satisfies the expectations invoked in at least one
+ /// condition from [conditions].
+ ///
+ /// Asynchronous expectations are not allowed in [conditions].
+ void anyOf(Iterable<Condition<T>> conditions) {
+ context.expect(
+ () => prefixFirst('matches any condition in ', literal(conditions)),
+ (actual) {
+ for (final condition in conditions) {
+ if (softCheck(actual, condition) == null) return null;
+ }
+ return Rejection(which: ['did not match any condition']);
+ });
+ }
+
+ /// Expects that the value is assignable to type [T].
+ ///
+ /// If the value is a [T], returns a [Subject] for further expectations.
+ Subject<R> isA<R>() {
+ return context.nest<R>(() => ['is a $R'], (actual) {
+ if (actual is! R) {
+ return Extracted.rejection(which: ['Is a ${actual.runtimeType}']);
+ }
+ return Extracted.value(actual);
+ }, atSameLevel: true);
+ }
+
+ /// Expects that the value is equal to [other] according to [operator ==].
+ void equals(T other) {
+ context.expect(() => prefixFirst('equals ', literal(other)), (actual) {
+ if (actual == other) return null;
+ return Rejection(which: ['are not equal']);
+ });
+ }
+
+ /// Expects that the value is [identical] to [other].
+ void identicalTo(T other) {
+ context.expect(() => prefixFirst('is identical to ', literal(other)),
+ (actual) {
+ if (identical(actual, other)) return null;
+ return Rejection(which: ['is not identical']);
+ });
+ }
+}
+
+extension BoolChecks on Subject<bool> {
+ void isTrue() {
+ context.expect(
+ () => ['is true'],
+ (actual) => actual
+ ? null // force coverage
+ : Rejection(),
+ );
+ }
+
+ void isFalse() {
+ context.expect(
+ () => ['is false'],
+ (actual) => !actual
+ ? null // force coverage
+ : Rejection(),
+ );
+ }
+}
+
+extension NullableChecks<T> on Subject<T?> {
+ Subject<T> isNotNull() {
+ return context.nest<T>(() => ['is not null'], (actual) {
+ if (actual == null) return Extracted.rejection();
+ return Extracted.value(actual);
+ }, atSameLevel: true);
+ }
+
+ void isNull() {
+ context.expect(() => const ['is null'], (actual) {
+ if (actual != null) return Rejection();
+ return null;
+ });
+ }
+}
+
+extension ComparableChecks<T> on Subject<Comparable<T>> {
+ /// Expects that this value is greater than [other].
+ void isGreaterThan(T other) {
+ context.expect(() => prefixFirst('is greater than ', literal(other)),
+ (actual) {
+ if (actual.compareTo(other) > 0) return null;
+ return Rejection(
+ which: prefixFirst('is not greater than ', literal(other)));
+ });
+ }
+
+ /// Expects that this value is greater than or equal to [other].
+ void isGreaterOrEqual(T other) {
+ context.expect(
+ () => prefixFirst('is greater than or equal to ', literal(other)),
+ (actual) {
+ if (actual.compareTo(other) >= 0) return null;
+ return Rejection(
+ which:
+ prefixFirst('is not greater than or equal to ', literal(other)));
+ });
+ }
+
+ /// Expects that this value is less than [other].
+ void isLessThan(T other) {
+ context.expect(() => prefixFirst('is less than ', literal(other)),
+ (actual) {
+ if (actual.compareTo(other) < 0) return null;
+ return Rejection(which: prefixFirst('is not less than ', literal(other)));
+ });
+ }
+
+ /// Expects that this value is less than or equal to [other].
+ void isLessOrEqual(T other) {
+ context
+ .expect(() => prefixFirst('is less than or equal to ', literal(other)),
+ (actual) {
+ if (actual.compareTo(other) <= 0) return null;
+ return Rejection(
+ which: prefixFirst('is not less than or equal to ', literal(other)));
+ });
+ }
+}
diff --git a/pkgs/checks/lib/src/extensions/function.dart b/pkgs/checks/lib/src/extensions/function.dart
new file mode 100644
index 0000000..300941c
--- /dev/null
+++ b/pkgs/checks/lib/src/extensions/function.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, 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 '../../context.dart';
+
+extension FunctionChecks<T> on Subject<T Function()> {
+ /// Expects that a function throws synchronously when it is called.
+ ///
+ /// If the function synchronously throws a value of type [E], return a
+ /// [Subject] to check further expectations on the error.
+ ///
+ /// If the function does not throw synchronously, or if it throws an error
+ /// that is not of type [E], this expectation will fail.
+ ///
+ /// If this function is async and returns a [Future], this expectation will
+ /// fail. Instead invoke the function and check the expectation on the
+ /// returned [Future].
+ Subject<E> throws<E>() {
+ return context.nest<E>(() => ['throws an error of type $E'], (actual) {
+ try {
+ final result = actual();
+ return Extracted.rejection(
+ actual: prefixFirst('a function that returned ', literal(result)),
+ which: ['did not throw'],
+ );
+ } catch (e) {
+ if (e is E) return Extracted.value(e as E);
+ return Extracted.rejection(
+ actual: prefixFirst('a function that threw error ', literal(e)),
+ which: ['did not throw an $E']);
+ }
+ });
+ }
+
+ /// Expects that the function returns without throwing.
+ ///
+ /// If the function runs without exception, return a [Subject] to check
+ /// further expecations on the returned value.
+ ///
+ /// If the function throws synchronously, this expectation will fail.
+ Subject<T> returnsNormally() {
+ return context.nest<T>(() => ['returns a value'], (actual) {
+ try {
+ return Extracted.value(actual());
+ } catch (e, st) {
+ return Extracted.rejection(actual: [
+ 'a function that throws'
+ ], which: [
+ ...prefixFirst('threw ', literal(e)),
+ ...st.toString().split('\n')
+ ]);
+ }
+ });
+ }
+}
diff --git a/pkgs/checks/lib/src/extensions/iterable.dart b/pkgs/checks/lib/src/extensions/iterable.dart
new file mode 100644
index 0000000..ecfa9c0
--- /dev/null
+++ b/pkgs/checks/lib/src/extensions/iterable.dart
@@ -0,0 +1,372 @@
+// Copyright (c) 2022, 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 '../../context.dart';
+
+import '../collection_equality.dart';
+import 'core.dart';
+
+extension IterableChecks<T> on Subject<Iterable<T>> {
+ Subject<int> get length => has((l) => l.length, 'length');
+
+ Subject<T> get first => context.nest(() => ['has first element'], (actual) {
+ final iterator = actual.iterator;
+ if (!iterator.moveNext()) {
+ return Extracted.rejection(which: ['has no elements']);
+ }
+ return Extracted.value(iterator.current);
+ });
+
+ Subject<T> get last => context.nest(() => ['has last element'], (actual) {
+ final iterator = actual.iterator;
+ if (!iterator.moveNext()) {
+ return Extracted.rejection(which: ['has no elements']);
+ }
+ var current = iterator.current;
+ while (iterator.moveNext()) {
+ current = iterator.current;
+ }
+ return Extracted.value(current);
+ });
+
+ Subject<T> get single => context.nest(() => ['has single element'], (actual) {
+ final iterator = actual.iterator;
+ if (!iterator.moveNext()) {
+ return Extracted.rejection(which: ['has no elements']);
+ }
+ final value = iterator.current;
+ if (iterator.moveNext()) {
+ return Extracted.rejection(which: ['has more than one element']);
+ }
+ return Extracted.value(value);
+ });
+
+ void isEmpty() {
+ context.expect(() => const ['is empty'], (actual) {
+ if (actual.isEmpty) return null;
+ return Rejection(which: ['is not empty']);
+ });
+ }
+
+ void isNotEmpty() {
+ context.expect(() => const ['is not empty'], (actual) {
+ if (actual.isNotEmpty) return null;
+ return Rejection(which: ['is empty']);
+ });
+ }
+
+ /// Expects that the iterable contains [element] according to
+ /// [Iterable.contains].
+ void contains(T element) {
+ context.expect(() {
+ return prefixFirst('contains ', literal(element));
+ }, (actual) {
+ if (actual.isEmpty) return Rejection(actual: ['an empty iterable']);
+ if (actual.contains(element)) return null;
+ return Rejection(
+ which: prefixFirst('does not contain ', literal(element)));
+ });
+ }
+
+ /// Expects that the iterable contains a value matching each expected value
+ /// from [elements] in the given order, with any extra elements between
+ /// them.
+ ///
+ /// For example, the following will succeed:
+ ///
+ /// ```dart
+ /// check([1, 0, 2, 0, 3]).containsInOrder([1, 2, 3]);
+ /// ```
+ ///
+ /// Values in [elements] may be a `T`, a `Condition<T>`, or a
+ /// `Condition<Object?>`. If an expectation is a condition callback it will be
+ /// checked against the actual values, and any other expectations, including
+ /// those that are not a `T` or a condition callback, will be compared with
+ /// the equality operator.
+ ///
+ /// ```dart
+ /// check([1, 0, 2, 0, 3])
+ /// .containsInOrder([1, (Subject<int> v) => v.isGreaterThan(1), 3]);
+ /// ```
+ @Deprecated('Use `containsEqualInOrder` for expectations with values compared'
+ ' with `==` or `containsMatchingInOrder` for other expectations')
+ void containsInOrder(Iterable<Object?> elements) {
+ context.expect(() => prefixFirst('contains, in order: ', literal(elements)),
+ (actual) {
+ final expected = elements.toList();
+ if (expected.isEmpty) {
+ throw ArgumentError('expected may not be empty');
+ }
+ var expectedIndex = 0;
+ for (final element in actual) {
+ final currentExpected = expected[expectedIndex];
+ final matches = currentExpected is Condition<T>
+ ? softCheck(element, currentExpected) == null
+ : currentExpected is Condition<dynamic>
+ ? softCheck(element, currentExpected) == null
+ : currentExpected == element;
+ if (matches && ++expectedIndex >= expected.length) return null;
+ }
+ return Rejection(which: [
+ ...prefixFirst(
+ 'did not have an element matching the expectation at index '
+ '$expectedIndex ',
+ literal(expected[expectedIndex])),
+ ]);
+ });
+ }
+
+ /// Expects that the iterable contains a value matching each condition in
+ /// [conditions] in the given order, with any extra elements between them.
+ ///
+ /// For example, the following will succeed:
+ ///
+ /// ```dart
+ /// check([1, 10, 2, 10, 3]).containsMatchingInOrder([
+ /// (it) => it.isLessThan(2),
+ /// (it) => it.isLessThan(3),
+ /// (it) => it.isLessThan(4),
+ /// ]);
+ /// ```
+ void containsMatchingInOrder(Iterable<Condition<T>> conditions) {
+ context
+ .expect(() => prefixFirst('contains, in order: ', literal(conditions)),
+ (actual) {
+ final expected = conditions.toList();
+ if (expected.isEmpty) {
+ throw ArgumentError('expected may not be empty');
+ }
+ var expectedIndex = 0;
+ for (final element in actual) {
+ final currentExpected = expected[expectedIndex];
+ final matches = softCheck(element, currentExpected) == null;
+ if (matches && ++expectedIndex >= expected.length) return null;
+ }
+ return Rejection(which: [
+ ...prefixFirst(
+ 'did not have an element matching the expectation at index '
+ '$expectedIndex ',
+ literal(expected[expectedIndex])),
+ ]);
+ });
+ }
+
+ /// Expects that the iterable contains a value equals to each expected value
+ /// from [elements] in the given order, with any extra elements between
+ /// them.
+ ///
+ /// For example, the following will succeed:
+ ///
+ /// ```dart
+ /// check([1, 0, 2, 0, 3]).containsInOrder([1, 2, 3]);
+ /// ```
+ ///
+ /// Values, will be compared with the equality operator.
+ void containsEqualInOrder(Iterable<T> elements) {
+ context.expect(() => prefixFirst('contains, in order: ', literal(elements)),
+ (actual) {
+ final expected = elements.toList();
+ if (expected.isEmpty) {
+ throw ArgumentError('expected may not be empty');
+ }
+ var expectedIndex = 0;
+ for (final element in actual) {
+ final currentExpected = expected[expectedIndex];
+ final matches = currentExpected == element;
+ if (matches && ++expectedIndex >= expected.length) return null;
+ }
+ return Rejection(which: [
+ ...prefixFirst(
+ 'did not have an element equal to the expectation at index '
+ '$expectedIndex ',
+ literal(expected[expectedIndex])),
+ ]);
+ });
+ }
+
+ /// Expects that the iterable contains at least on element such that
+ /// [elementCondition] is satisfied.
+ void any(Condition<T> elementCondition) {
+ context.expect(() {
+ final conditionDescription = describe(elementCondition);
+ assert(conditionDescription.isNotEmpty);
+ return [
+ 'contains a value that:',
+ ...conditionDescription,
+ ];
+ }, (actual) {
+ if (actual.isEmpty) return Rejection(actual: ['an empty iterable']);
+ for (var e in actual) {
+ if (softCheck(e, elementCondition) == null) return null;
+ }
+ return Rejection(which: ['Contains no matching element']);
+ });
+ }
+
+ /// Expects there are no elements in the iterable which fail to satisfy
+ /// [elementCondition].
+ ///
+ /// Empty iterables will pass always pass this expectation.
+ void every(Condition<T> elementCondition) {
+ context.expect(() {
+ final conditionDescription = describe(elementCondition);
+ assert(conditionDescription.isNotEmpty);
+ return [
+ 'only has values that:',
+ ...conditionDescription,
+ ];
+ }, (actual) {
+ final iterator = actual.iterator;
+ for (var i = 0; iterator.moveNext(); i++) {
+ final element = iterator.current;
+ final failure = softCheck(element, elementCondition);
+ if (failure == null) continue;
+ final which = failure.rejection.which;
+ return Rejection(which: [
+ 'has an element at index $i that:',
+ ...indent(failure.detail.actual.skip(1)),
+ ...indent(prefixFirst('Actual: ', failure.rejection.actual),
+ failure.detail.depth + 1),
+ if (which != null && which.isNotEmpty)
+ ...indent(prefixFirst('Which: ', which), failure.detail.depth + 1),
+ ]);
+ }
+ return null;
+ });
+ }
+
+ /// Expects that the iterable contains elements that are deeply equal to the
+ /// elements of [expected].
+ ///
+ /// {@macro deep_collection_equals}
+ void deepEquals(Iterable<Object?> expected) => context
+ .expect(() => prefixFirst('is deeply equal to ', literal(expected)),
+ (actual) {
+ final which = deepCollectionEquals(actual, expected);
+ if (which == null) return null;
+ return Rejection(which: which);
+ });
+
+ /// Expects that the iterable contains elements which equal those of
+ /// [expected] in any order.
+ ///
+ /// Should not be used for very large collections, runtime is O(n^2.5) in the
+ /// worst case where the iterables contain many equal elements, and O(n^2) in
+ /// more typical cases.
+ void unorderedEquals(Iterable<T> expected) {
+ context.expect(() => prefixFirst('unordered equals ', literal(expected)),
+ (actual) {
+ final which = unorderedCompare(
+ actual,
+ expected,
+ (actual, expected) => expected == actual,
+ (expected, index, count) => [
+ ...prefixFirst(
+ 'has no element equal to the expected element at index '
+ '$index: ',
+ literal(expected)),
+ if (count > 1) 'or ${count - 1} other elements',
+ ],
+ (actual, index, count) => [
+ ...prefixFirst(
+ 'has an unexpected element at index $index: ', literal(actual)),
+ if (count > 1) 'and ${count - 1} other unexpected elements',
+ ],
+ );
+ if (which == null) return null;
+ return Rejection(which: which);
+ });
+ }
+
+ /// Expects that the iterable contains elements which match all conditions of
+ /// [expected] in any order.
+ ///
+ /// Should not be used for very large collections, runtime is O(n^2.5) in the
+ /// worst case where conditions match many elements, and O(n^2) in more
+ /// typical cases.
+ void unorderedMatches(Iterable<Condition<T>> expected) {
+ context.expect(() => prefixFirst('unordered matches ', literal(expected)),
+ (actual) {
+ final which = unorderedCompare(
+ actual,
+ expected,
+ (actual, expected) => softCheck(actual, expected) == null,
+ (expected, index, count) => [
+ 'has no element matching the condition at index $index:',
+ ...describe(expected),
+ if (count > 1) 'or ${count - 1} other conditions',
+ ],
+ (actual, index, count) => [
+ ...prefixFirst(
+ 'has an unmatched element at index $index: ', literal(actual)),
+ if (count > 1) 'and ${count - 1} other unmatched elements',
+ ],
+ );
+ if (which == null) return null;
+ return Rejection(which: which);
+ });
+ }
+
+ /// Expects that the iterable contains elements that correspond by the
+ /// [elementCondition] exactly to each element in [expected].
+ ///
+ /// Fails if the iterable has a different length than [expected].
+ ///
+ /// For each element in the iterable, calls [elementCondition] with the
+ /// corresponding element from [expected] to get the specific condition for
+ /// that index.
+ ///
+ /// [description] is used in the Expected clause. It should be a predicate
+ /// without the object, for example with the description 'is less than' the
+ /// full expectation will be: "pairwise is less than $expected"
+ @Deprecated('Use `pairwiseMatches`')
+ void pairwiseComparesTo<S>(List<S> expected,
+ Condition<T> Function(S) elementCondition, String description) =>
+ pairwiseMatches(expected, elementCondition, description);
+
+ /// Expects that the iterable contains elements that correspond by the
+ /// [elementCondition] exactly to each element in [expected].
+ ///
+ /// Fails if the iterable has a different length than [expected].
+ ///
+ /// For each element in the iterable, calls [elementCondition] with the
+ /// corresponding element from [expected] to get the specific condition for
+ /// that index.
+ ///
+ /// [description] is used in the Expected clause. It should be a predicate
+ /// without the object, for example with the description 'is less than' the
+ /// full expectation will be: "pairwise is less than $expected"
+ void pairwiseMatches<S>(List<S> expected,
+ Condition<T> Function(S) elementCondition, String description) {
+ context.expect(() {
+ return prefixFirst('pairwise $description ', literal(expected));
+ }, (actual) {
+ final iterator = actual.iterator;
+ for (var i = 0; i < expected.length; i++) {
+ final expectedValue = expected[i];
+ if (!iterator.moveNext()) {
+ return Rejection(which: [
+ 'has too few elements, there is no element to match at index $i'
+ ]);
+ }
+ final actualValue = iterator.current;
+ final failure = softCheck(actualValue, elementCondition(expectedValue));
+ if (failure == null) continue;
+ final innerDescription = describe<T>(elementCondition(expectedValue));
+ final which = failure.rejection.which;
+ return Rejection(which: [
+ 'does not have an element at index $i that:',
+ ...innerDescription,
+ ...prefixFirst(
+ 'Actual element at index $i: ', failure.rejection.actual),
+ if (which != null) ...prefixFirst('Which: ', which),
+ ]);
+ }
+ if (!iterator.moveNext()) return null;
+ return Rejection(which: [
+ 'has too many elements, expected exactly ${expected.length}'
+ ]);
+ });
+ }
+}
diff --git a/pkgs/checks/lib/src/extensions/map.dart b/pkgs/checks/lib/src/extensions/map.dart
new file mode 100644
index 0000000..4fc0fdf
--- /dev/null
+++ b/pkgs/checks/lib/src/extensions/map.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2022, 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 '../../context.dart';
+
+import '../collection_equality.dart';
+import 'core.dart';
+
+extension MapChecks<K, V> on Subject<Map<K, V>> {
+ Subject<Iterable<MapEntry<K, V>>> get entries =>
+ has((m) => m.entries, 'entries');
+ Subject<Iterable<K>> get keys => has((m) => m.keys, 'keys');
+ Subject<Iterable<V>> get values => has((m) => m.values, 'values');
+ Subject<int> get length => has((m) => m.length, 'length');
+ Subject<V> operator [](K key) {
+ return context.nest(
+ () => prefixFirst('contains a value for ', literal(key)), (actual) {
+ if (!actual.containsKey(key)) {
+ return Extracted.rejection(
+ which: prefixFirst('does not contain the key ', literal(key)));
+ }
+ return Extracted.value(actual[key] as V);
+ });
+ }
+
+ void isEmpty() {
+ context.expect(() => const ['is empty'], (actual) {
+ if (actual.isEmpty) return null;
+ return Rejection(which: ['is not empty']);
+ });
+ }
+
+ void isNotEmpty() {
+ context.expect(() => const ['is not empty'], (actual) {
+ if (actual.isNotEmpty) return null;
+ return Rejection(which: ['is not empty']);
+ });
+ }
+
+ /// Expects that the map contains [key] according to [Map.containsKey].
+ void containsKey(K key) {
+ context.expect(() => prefixFirst('contains key ', literal(key)), (actual) {
+ if (actual.containsKey(key)) return null;
+ return Rejection(
+ which: prefixFirst('does not contain key ', literal(key)));
+ });
+ }
+
+ /// Expects that the map contains some key such that [keyCondition] is
+ /// satisfied.
+ void containsKeyThat(Condition<K> keyCondition) {
+ context.expect(() {
+ final conditionDescription = describe(keyCondition);
+ assert(conditionDescription.isNotEmpty);
+ return [
+ 'contains a key that:',
+ ...conditionDescription,
+ ];
+ }, (actual) {
+ if (actual.isEmpty) return Rejection(actual: ['an empty map']);
+ for (var k in actual.keys) {
+ if (softCheck(k, keyCondition) == null) return null;
+ }
+ return Rejection(which: ['Contains no matching key']);
+ });
+ }
+
+ /// Expects that the map contains [value] according to [Map.containsValue].
+ void containsValue(V value) {
+ context.expect(() => prefixFirst('contains value ', literal(value)),
+ (actual) {
+ if (actual.containsValue(value)) return null;
+ return Rejection(
+ which: prefixFirst('does not contain value ', literal(value)));
+ });
+ }
+
+ /// Expects that the map contains some value such that [valueCondition] is
+ /// satisfied.
+ void containsValueThat(Condition<V> valueCondition) {
+ context.expect(() {
+ final conditionDescription = describe(valueCondition);
+ assert(conditionDescription.isNotEmpty);
+ return [
+ 'contains a value that:',
+ ...conditionDescription,
+ ];
+ }, (actual) {
+ if (actual.isEmpty) return Rejection(actual: ['an empty map']);
+ for (var v in actual.values) {
+ if (softCheck(v, valueCondition) == null) return null;
+ }
+ return Rejection(which: ['Contains no matching value']);
+ });
+ }
+
+ /// Expects that the map contains entries that are deeply equal to the entries
+ /// of [expected].
+ ///
+ /// {@macro deep_collection_equals}
+ void deepEquals(Map<Object?, Object?> expected) => context
+ .expect(() => prefixFirst('is deeply equal to ', literal(expected)),
+ (actual) {
+ final which = deepCollectionEquals(actual, expected);
+ if (which == null) return null;
+ return Rejection(which: which);
+ });
+}
diff --git a/pkgs/checks/lib/src/extensions/math.dart b/pkgs/checks/lib/src/extensions/math.dart
new file mode 100644
index 0000000..302f1db
--- /dev/null
+++ b/pkgs/checks/lib/src/extensions/math.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2022, 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 '../../context.dart';
+
+extension NumChecks on Subject<num> {
+ /// Expects that [num.isNaN] is true.
+ void isNaN() {
+ context.expect(() => ['is not a number (NaN)'], (actual) {
+ if (actual.isNaN) return null;
+ return Rejection(which: ['is a number']);
+ });
+ }
+
+ /// Expects that [num.isNaN] is false.
+ void isNotNaN() {
+ context.expect(() => ['is a number (not NaN)'], (actual) {
+ if (!actual.isNaN) return null;
+ return Rejection(which: ['is not a number (NaN)']);
+ });
+ }
+
+ /// Expects that [num.isNegative] is true.
+ void isNegative() {
+ context.expect(() => ['is negative'], (actual) {
+ if (actual.isNegative) return null;
+ return Rejection(which: ['is not negative']);
+ });
+ }
+
+ /// Expects that [num.isNegative] is false.
+ void isNotNegative() {
+ context.expect(() => ['is not negative'], (actual) {
+ if (!actual.isNegative) return null;
+ return Rejection(which: ['is negative']);
+ });
+ }
+
+ /// Expects that [num.isFinite] is true.
+ void isFinite() {
+ context.expect(() => ['is finite'], (actual) {
+ if (actual.isFinite) return null;
+ return Rejection(which: ['is not finite']);
+ });
+ }
+
+ /// Expects that [num.isFinite] is false.
+ ///
+ /// Satisfied by [double.nan], [double.infinity] and
+ /// [double.negativeInfinity].
+ void isNotFinite() {
+ context.expect(() => ['is not finite'], (actual) {
+ if (!actual.isFinite) return null;
+ return Rejection(which: ['is finite']);
+ });
+ }
+
+ /// Expects that [num.isInfinite] is true.
+ ///
+ /// Satisfied by [double.infinity] and [double.negativeInfinity].
+ void isInfinite() {
+ context.expect(() => ['is infinite'], (actual) {
+ if (actual.isInfinite) return null;
+ return Rejection(which: ['is not infinite']);
+ });
+ }
+
+ /// Expects that [num.isInfinite] is false.
+ ///
+ /// Satisfied by [double.nan] and finite numbers.
+ void isNotInfinite() {
+ context.expect(() => ['is not infinite'], (actual) {
+ if (!actual.isInfinite) return null;
+ return Rejection(which: ['is infinite']);
+ });
+ }
+
+ /// Expects that the difference between this number and [other] is less than
+ /// or equal to [delta].
+ void isCloseTo(num other, num delta) {
+ context.expect(() => ['is within <$delta> of <$other>'], (actual) {
+ final difference = (other - actual).abs();
+ if (difference <= delta) return null;
+ return Rejection(which: ['differs by <$difference>']);
+ });
+ }
+}
diff --git a/pkgs/checks/lib/src/extensions/string.dart b/pkgs/checks/lib/src/extensions/string.dart
new file mode 100644
index 0000000..84b20f6
--- /dev/null
+++ b/pkgs/checks/lib/src/extensions/string.dart
@@ -0,0 +1,229 @@
+// Copyright (c) 2022, 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:math' as math;
+
+import '../../context.dart';
+
+import 'core.dart';
+
+extension StringChecks on Subject<String> {
+ /// Expects that the value contains [pattern] according to [String.contains];
+ void contains(Pattern pattern) {
+ context.expect(() => prefixFirst('contains ', literal(pattern)), (actual) {
+ if (actual.contains(pattern)) return null;
+ return Rejection(
+ which: prefixFirst('Does not contain ', literal(pattern)),
+ );
+ });
+ }
+
+ Subject<int> get length => has((m) => m.length, 'length');
+
+ void isEmpty() {
+ context.expect(() => const ['is empty'], (actual) {
+ if (actual.isEmpty) return null;
+ return Rejection(which: ['is not empty']);
+ });
+ }
+
+ void isNotEmpty() {
+ context.expect(() => const ['is not empty'], (actual) {
+ if (actual.isNotEmpty) return null;
+ return Rejection(which: ['is empty']);
+ });
+ }
+
+ void startsWith(Pattern other) {
+ context.expect(
+ () => prefixFirst('starts with ', literal(other)),
+ (actual) {
+ if (actual.startsWith(other)) return null;
+ return Rejection(
+ which: prefixFirst('does not start with ', literal(other)),
+ );
+ },
+ );
+ }
+
+ void endsWith(String other) {
+ context.expect(
+ () => prefixFirst('ends with ', literal(other)),
+ (actual) {
+ if (actual.endsWith(other)) return null;
+ return Rejection(
+ which: prefixFirst('does not end with ', literal(other)),
+ );
+ },
+ );
+ }
+
+ /// Expects that the string matches the pattern [expected].
+ ///
+ /// Fails if [expected] returns an empty result from calling `allMatches` with
+ /// the value.
+ ///
+ /// ```
+ /// check(actual).matchesPattern('abc');
+ /// check(actual).matchesPattern(RegExp(r'\d'));
+ /// ```
+ void matchesPattern(Pattern expected) {
+ context.expect(() => prefixFirst('matches ', literal(expected)), (actual) {
+ if (expected.allMatches(actual).isNotEmpty) return null;
+ return Rejection(
+ which: prefixFirst('does not match ', literal(expected)));
+ });
+ }
+
+ /// Expects that the `String` contains each of the sub strings in expected
+ /// in the given order, with any content between them.
+ ///
+ /// For example, the following will succeed:
+ ///
+ /// check('abcdefg').containsInOrder(['a','e']);
+ void containsInOrder(Iterable<String> expected) {
+ context.expect(() => prefixFirst('contains, in order: ', literal(expected)),
+ (actual) {
+ var fromIndex = 0;
+ for (var s in expected) {
+ var index = actual.indexOf(s, fromIndex);
+ if (index < 0) {
+ return Rejection(which: [
+ ...prefixFirst(
+ 'does not have a match for the substring ', literal(s)),
+ if (fromIndex != 0)
+ 'following the other matches up to character $fromIndex'
+ ]);
+ }
+ fromIndex = index + s.length;
+ }
+ return null;
+ });
+ }
+
+ /// Expects that the `String` contains exactly the same code units as
+ /// [expected].
+ void equals(String expected) {
+ context.expect(() => prefixFirst('equals ', literal(expected)),
+ (actual) => _findDifference(actual, expected));
+ }
+
+ /// Expects that the `String` contains the same characters as [expected] if
+ /// both were lower case.
+ void equalsIgnoringCase(String expected) {
+ context.expect(
+ () => prefixFirst('equals ignoring case ', literal(expected)),
+ (actual) => _findDifference(
+ actual.toLowerCase(), expected.toLowerCase(), actual, expected));
+ }
+
+ /// Expects that the `String` contains the same content as [expected],
+ /// ignoring differences in whitsepace.
+ ///
+ /// All runs of whitespace characters are collapsed to a single space, and
+ /// leading and traiilng whitespace are removed before comparison.
+ ///
+ /// For example the following will succeed:
+ ///
+ /// check(' hello world ').equalsIgnoringWhitespace('hello world');
+ ///
+ /// While the following will fail:
+ ///
+ /// check('helloworld').equalsIgnoringWhitespace('hello world');
+ /// check('he llo world').equalsIgnoringWhitespace('hello world');
+ void equalsIgnoringWhitespace(String expected) {
+ context.expect(
+ () => prefixFirst('equals ignoring whitespace ', literal(expected)),
+ (actual) {
+ final collapsedActual = _collapseWhitespace(actual);
+ final collapsedExpected = _collapseWhitespace(expected);
+ return _findDifference(collapsedActual, collapsedExpected,
+ collapsedActual, collapsedExpected);
+ });
+ }
+}
+
+Rejection? _findDifference(String actual, String expected,
+ [String? actualDisplay, String? expectedDisplay]) {
+ if (actual == expected) return null;
+ final escapedActual = escape(actual);
+ final escapedExpected = escape(expected);
+ final escapedActualDisplay =
+ actualDisplay != null ? escape(actualDisplay) : escapedActual;
+ final escapedExpectedDisplay =
+ expectedDisplay != null ? escape(expectedDisplay) : escapedExpected;
+ final minLength = math.min(escapedActual.length, escapedExpected.length);
+ var i = 0;
+ for (; i < minLength; i++) {
+ if (escapedActual.codeUnitAt(i) != escapedExpected.codeUnitAt(i)) {
+ break;
+ }
+ }
+ if (i == minLength) {
+ if (escapedExpected.length < escapedActual.length) {
+ if (expected.isEmpty) {
+ return Rejection(which: ['is not the empty string']);
+ }
+ return Rejection(which: [
+ 'is too long with unexpected trailing characters:',
+ _trailing(escapedActualDisplay, i)
+ ]);
+ } else {
+ if (actual.isEmpty) {
+ return Rejection(actual: [
+ 'an empty string'
+ ], which: [
+ 'is missing all expected characters:',
+ _trailing(escapedExpectedDisplay, 0)
+ ]);
+ }
+ return Rejection(which: [
+ 'is too short with missing trailing characters:',
+ _trailing(escapedExpectedDisplay, i)
+ ]);
+ }
+ } else {
+ final indentation = ' ' * (i > 10 ? 14 : i);
+ return Rejection(which: [
+ 'differs at offset $i:',
+ '${_leading(escapedExpectedDisplay, i)}'
+ '${_trailing(escapedExpectedDisplay, i)}',
+ '${_leading(escapedActualDisplay, i)}'
+ '${_trailing(escapedActualDisplay, i)}',
+ '$indentation^'
+ ]);
+ }
+}
+
+/// The truncated beginning of [s] up to the [end] character.
+String _leading(String s, int end) =>
+ (end > 10) ? '... ${s.substring(end - 10, end)}' : s.substring(0, end);
+
+/// The truncated remainder of [s] starting at the [start] character.
+String _trailing(String s, int start) => (start + 10 > s.length)
+ ? s.substring(start)
+ : '${s.substring(start, start + 10)} ...';
+
+/// Utility function to collapse whitespace runs to single spaces
+/// and strip leading/trailing whitespace.
+String _collapseWhitespace(String string) {
+ var result = StringBuffer();
+ var skipSpace = true;
+ for (var i = 0; i < string.length; i++) {
+ var character = string[i];
+ if (_isWhitespace(character)) {
+ if (!skipSpace) {
+ result.write(' ');
+ skipSpace = true;
+ }
+ } else {
+ result.write(character);
+ skipSpace = false;
+ }
+ }
+ return result.toString().trim();
+}
+
+bool _isWhitespace(String ch) =>
+ ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
diff --git a/pkgs/checks/mono_pkg.yaml b/pkgs/checks/mono_pkg.yaml
new file mode 100644
index 0000000..b447edb
--- /dev/null
+++ b/pkgs/checks/mono_pkg.yaml
@@ -0,0 +1,15 @@
+# See https://pub.dev/packages/mono_repo
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+ sdk: dev
+ - group:
+ - analyze
+ sdk: pubspec
+- unit_test:
+ - group:
+ - command: dart test
+ sdk: [dev, pubspec]
diff --git a/pkgs/checks/pubspec.yaml b/pkgs/checks/pubspec.yaml
new file mode 100644
index 0000000..8c59ba4
--- /dev/null
+++ b/pkgs/checks/pubspec.yaml
@@ -0,0 +1,18 @@
+name: checks
+version: 0.3.1-wip
+description: >-
+ A framework for checking values against expectations and building custom
+ expectations.
+repository: https://github.com/dart-lang/test/tree/master/pkgs/checks
+resolution: workspace
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ async: ^2.8.0
+ meta: ^1.9.0
+ test_api: ">=0.5.0 <0.8.0"
+
+dev_dependencies:
+ test: ^1.21.3
diff --git a/pkgs/checks/test/context_test.dart b/pkgs/checks/test/context_test.dart
new file mode 100644
index 0000000..820ff88
--- /dev/null
+++ b/pkgs/checks/test/context_test.dart
@@ -0,0 +1,172 @@
+// 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 'package:async/async.dart' hide Result;
+import 'package:checks/checks.dart';
+import 'package:checks/context.dart';
+import 'package:test/scaffolding.dart';
+import 'package:test_api/hooks.dart';
+import 'package:test_api/hooks_testing.dart';
+
+void main() {
+ group('Context', () {
+ test('expectAsync holds test open', () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ check(null).context.expectAsync(() => [''], (actual) async {
+ final completer = Completer<void>();
+ callback = completer.complete;
+ await completer.future;
+ return null;
+ });
+ });
+ await pumpEventQueue();
+ check(monitor).state.equals(State.running);
+ callback();
+ await monitor.onDone;
+ check(monitor).didPass();
+ });
+
+ test('expectAsync does not hold test open past exception', () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ check(null).context.expectAsync(() => [''], (actual) async {
+ final completer = Completer<void>();
+ callback = completer.complete;
+ await completer.future;
+ throw 'oh no!';
+ });
+ });
+ await pumpEventQueue();
+ check(monitor).state.equals(State.running);
+ callback();
+ await monitor.onDone;
+ check(monitor)
+ ..state.equals(State.failed)
+ ..errors.single.has((e) => e.error, 'error').equals('oh no!');
+ });
+
+ test('nestAsync holds test open', () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ check(null).context.nestAsync(() => [''], (actual) async {
+ final completer = Completer<void>();
+ callback = completer.complete;
+ await completer.future;
+ return Extracted.value(null);
+ }, null);
+ });
+ await pumpEventQueue();
+ check(monitor).state.equals(State.running);
+ callback();
+ await monitor.onDone;
+ check(monitor).didPass();
+ });
+
+ test('nestAsync holds test open past async condition', () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ check(null).context.nestAsync(() => [''], (actual) async {
+ return Extracted.value(null);
+ }, (it) async {
+ final completer = Completer<void>();
+ callback = completer.complete;
+ await completer.future;
+ });
+ });
+ await pumpEventQueue();
+ check(monitor).state.equals(State.running);
+ callback();
+ await monitor.onDone;
+ check(monitor).didPass();
+ });
+
+ test('nestAsync does not hold test open past exception', () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ check(null).context.nestAsync<Object?>(() => [''], (actual) async {
+ final completer = Completer<void>();
+ callback = completer.complete;
+ await completer.future;
+ throw 'oh no!';
+ }, null);
+ });
+ await pumpEventQueue();
+ check(monitor).state.equals(State.running);
+ callback();
+ await monitor.onDone;
+ check(monitor)
+ ..state.equals(State.failed)
+ ..errors.single.has((e) => e.error, 'error').equals('oh no!');
+ });
+
+ test('expectUnawaited can fail the test after it completes', () async {
+ late void Function() callback;
+ final monitor = await TestCaseMonitor.run(() {
+ check(null).context.expectUnawaited(() => [''], (actual, reject) {
+ final completer = Completer<void>()
+ ..future.then((_) {
+ reject(Rejection(which: ['foo']));
+ });
+ callback = completer.complete;
+ });
+ });
+ check(monitor).state.equals(State.passed);
+ callback();
+ await pumpEventQueue();
+ check(monitor)
+ ..state.equals(State.failed)
+ ..errors.unorderedMatches([
+ (it) => it
+ .has((e) => e.error, 'error')
+ .isA<TestFailure>()
+ .has((f) => f.message, 'message')
+ .isNotNull()
+ .endsWith('Which: foo'),
+ (it) => it
+ .has((e) => e.error, 'error')
+ .isA<String>()
+ .startsWith('This test failed after it had already completed.')
+ ]);
+ });
+ });
+
+ group('SkipExtension', () {
+ test('marks the test as skipped', () async {
+ final monitor = await TestCaseMonitor.run(() {
+ check(null).skip('skip').isNotNull();
+ });
+ check(monitor).state.equals(State.skipped);
+ });
+ });
+}
+
+extension _MonitorChecks on Subject<TestCaseMonitor> {
+ Subject<State> get state => has((m) => m.state, 'state');
+ Subject<Iterable<AsyncError>> get errors => has((m) => m.errors, 'errors');
+ Subject<StreamQueue<AsyncError>> get onError =>
+ has((m) => m.onError, 'onError').withQueue;
+
+ /// Expects that the monitored test is completed as success with no errors.
+ ///
+ /// Sets up an unawaited expectation that the test does not emit errors in the
+ /// future in addition to checking there have been no errors yet.
+ void didPass() {
+ errors.isEmpty();
+ state.equals(State.passed);
+ onError.context.expectUnawaited(() => ['emits no further errors'],
+ (actual, reject) async {
+ await for (var error in actual.rest) {
+ reject(Rejection(which: [
+ ...prefixFirst('threw late error', literal(error.error)),
+ ...const LineSplitter().convert(
+ TestHandle.current.formatStackTrace(error.stackTrace).toString())
+ ]));
+ }
+ });
+ }
+}
diff --git a/pkgs/checks/test/describe_test.dart b/pkgs/checks/test/describe_test.dart
new file mode 100644
index 0000000..d117a7b
--- /dev/null
+++ b/pkgs/checks/test/describe_test.dart
@@ -0,0 +1,24 @@
+// 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 'package:checks/checks.dart';
+import 'package:checks/context.dart';
+import 'package:test/scaffolding.dart';
+
+void main() {
+ group('describe', () {
+ test('succeeds for empty conditions', () {
+ check(describe((_) {})).isEmpty();
+ });
+ test('includes condition clauses', () {
+ check(describe((it) => it.equals(1))).deepEquals([' equals <1>']);
+ });
+ test('includes nested clauses', () {
+ check(describe<String>((it) => it.length.equals(1))).deepEquals([
+ ' has length that:',
+ ' equals <1>',
+ ]);
+ });
+ });
+}
diff --git a/pkgs/checks/test/extensions/async_test.dart b/pkgs/checks/test/extensions/async_test.dart
new file mode 100644
index 0000000..5bfe550
--- /dev/null
+++ b/pkgs/checks/test/extensions/async_test.dart
@@ -0,0 +1,531 @@
+// Copyright (c) 2022, 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 'package:async/async.dart';
+import 'package:checks/checks.dart';
+import 'package:checks/context.dart';
+import 'package:test/scaffolding.dart';
+import 'package:test_api/hooks.dart';
+
+import '../test_shared.dart';
+
+void main() {
+ group('FutureChecks', () {
+ group('completes', () {
+ test('succeeds for a future that completes to a value', () async {
+ await check(_futureSuccess()).completes((it) => it.equals(42));
+ });
+ test('rejects futures which complete as errors', () async {
+ await check(_futureFail()).isRejectedByAsync(
+ (it) => it.completes((it) => it.equals(1)),
+ actual: ['a future that completes as an error'],
+ which: ['threw <UnimplementedError> at:', 'fake trace'],
+ );
+ });
+ test('can be described', () async {
+ await check((Subject<Future> it) => it.completes())
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' completes to a value']));
+ await check((Subject<Future> it) => it.completes((it) => it.equals(42)))
+ .hasAsyncDescriptionWhich((it) => it.deepEquals([
+ ' completes to a value that:',
+ ' equals <42>',
+ ]));
+ });
+ });
+
+ group('throws', () {
+ test(
+ 'succeeds for a future that compeletes to an error of the expected type',
+ () async {
+ await check(_futureFail()).throws<UnimplementedError>(
+ (it) => it.has((p0) => p0.message, 'message').isNull());
+ });
+ test('fails for futures that complete to a value', () async {
+ await check(_futureSuccess()).isRejectedByAsync(
+ (it) => it.throws(),
+ actual: ['completed to <42>'],
+ which: ['did not throw'],
+ );
+ });
+ test('failes for futures that complete to an error of the wrong type',
+ () async {
+ await check(_futureFail()).isRejectedByAsync(
+ (it) => it.throws<StateError>(),
+ actual: ['completed to error <UnimplementedError>'],
+ which: [
+ 'threw an exception that is not a StateError at:',
+ 'fake trace'
+ ],
+ );
+ });
+ test('can be described', () async {
+ await check((Subject<Future<void>> it) => it.throws())
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' completes to an error']));
+ await check((Subject<Future<void>> it) => it.throws<StateError>())
+ .hasAsyncDescriptionWhich((it) =>
+ it.deepEquals([' completes to an error of type StateError']));
+ });
+ });
+
+ group('doesNotComplete', () {
+ test('succeeds for a Future that never completes', () async {
+ check(Completer<void>().future).doesNotComplete();
+ });
+ test('fails for a Future that completes as a value', () async {
+ Object? testFailure;
+ runZonedGuarded(() {
+ final completer = Completer<String>();
+ check(completer.future).doesNotComplete();
+ completer.complete('value');
+ }, (e, st) {
+ testFailure = e;
+ });
+ await pumpEventQueue();
+ check(testFailure)
+ .isA<TestFailure>()
+ .has((f) => f.message, 'message')
+ .isNotNull()
+ .equals('''
+Expected: a Future<String> that:
+ does not complete
+Actual: a future that completed to 'value\'''');
+ });
+ test('fails for a Future that completes as an error', () async {
+ Object? testFailure;
+ runZonedGuarded(() {
+ final completer = Completer<String>();
+ check(completer.future).doesNotComplete();
+ completer.completeError('error', StackTrace.fromString('fake trace'));
+ }, (e, st) {
+ testFailure = e;
+ });
+ await pumpEventQueue();
+ check(testFailure)
+ .isA<TestFailure>()
+ .has((f) => f.message, 'message')
+ .isNotNull()
+ .equals('''
+Expected: a Future<String> that:
+ does not complete
+Actual: a future that completed as an error:
+Which: threw 'error'
+fake trace''');
+ });
+ test('can be described', () async {
+ await check((Subject<Future<void>> it) => it.doesNotComplete())
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' does not complete']));
+ });
+ });
+ });
+
+ group('StreamChecks', () {
+ group('emits', () {
+ test('succeeds for a stream that emits a value', () async {
+ await check(_countingStream(5)).emits((it) => it.equals(0));
+ });
+ test('fails for a stream that closes without emitting', () async {
+ await check(_countingStream(0)).isRejectedByAsync(
+ (it) => it.emits(),
+ actual: ['a stream'],
+ which: ['closed without emitting enough values'],
+ );
+ });
+ test('fails for a stream that emits an error', () async {
+ await check(_countingStream(1, errorAt: 0)).isRejectedByAsync(
+ (it) => it.emits(),
+ actual: ['a stream with error <UnimplementedError: Error at 1>'],
+ which: ['emitted an error instead of a value at:', 'fake trace'],
+ );
+ });
+ test('can be described', () async {
+ await check((Subject<StreamQueue<void>> it) => it.emits())
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' emits a value']));
+ await check((Subject<StreamQueue<int>> it) =>
+ it.emits((it) => it.equals(42)))
+ .hasAsyncDescriptionWhich((it) => it.deepEquals([
+ ' emits a value that:',
+ ' equals <42>',
+ ]));
+ });
+ test('does not consume error', () async {
+ final queue = _countingStream(1, errorAt: 0);
+ await softCheckAsync<StreamQueue<int>>(queue, (it) => it.emits());
+ await check(queue).emitsError();
+ });
+ });
+
+ group('emitsError', () {
+ test('succeeds for a stream that emits an error', () async {
+ await check(_countingStream(1, errorAt: 0))
+ .emitsError<UnimplementedError>();
+ });
+ test('fails for a stream that closes without emitting an error',
+ () async {
+ await check(_countingStream(0)).isRejectedByAsync(
+ (it) => it.emitsError(),
+ actual: ['a stream'],
+ which: ['closed without emitting an expected error'],
+ );
+ });
+ test('fails for a stream that emits value', () async {
+ await check(_countingStream(1)).isRejectedByAsync(
+ (it) => it.emitsError(),
+ actual: ['a stream emitting value <0>'],
+ which: ['closed without emitting an error'],
+ );
+ });
+ test('fails for a stream that emits an error of the incorrect type',
+ () async {
+ await check(_countingStream(1, errorAt: 0)).isRejectedByAsync(
+ (it) => it.emitsError<StateError>(),
+ actual: ['a stream with error <UnimplementedError: Error at 1>'],
+ which: ['emitted an error which is not StateError at:', 'fake trace'],
+ );
+ });
+ test('can be described', () async {
+ await check((Subject<StreamQueue<void>> it) => it.emitsError())
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' emits an error']));
+ await check(
+ (Subject<StreamQueue<void>> it) => it.emitsError<StateError>())
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' emits an error of type StateError']));
+ await check((Subject<StreamQueue<void>> it) => it
+ ..emitsError<StateError>(
+ (it) => it.has((e) => e.message, 'message').equals('foo')))
+ .hasAsyncDescriptionWhich((it) => it.deepEquals([
+ ' emits an error of type StateError that:',
+ ' has message that:',
+ ' equals \'foo\''
+ ]));
+ });
+ test('uses a transaction', () async {
+ final queue = _countingStream(1);
+ await softCheckAsync<StreamQueue<int>>(queue, (it) => it.emitsError());
+ await check(queue).emits((it) => it.equals(0));
+ });
+ });
+
+ group('emitsThrough', () {
+ test('succeeds for a stream that eventuall emits a matching value',
+ () async {
+ await check(_countingStream(5)).emitsThrough((it) => it.equals(4));
+ });
+ test('fails for a stream that closes without emitting a matching value',
+ () async {
+ await check(_countingStream(4)).isRejectedByAsync(
+ (it) => it.emitsThrough((it) => it.equals(5)),
+ actual: ['a stream'],
+ which: ['ended after emitting 4 elements with none matching'],
+ );
+ });
+ test('can be described', () async {
+ await check((Subject<StreamQueue<int>> it) =>
+ it.emitsThrough((it) => it.equals(42)))
+ .hasAsyncDescriptionWhich((it) => it.deepEquals([
+ ' emits any values then emits a value that:',
+ ' equals <42>'
+ ]));
+ });
+ test('uses a transaction', () async {
+ final queue = _countingStream(1);
+ await softCheckAsync(
+ queue,
+ (Subject<StreamQueue<int>> it) =>
+ it.emitsThrough((it) => it.equals(42)));
+ await check(queue).emits((it) => it.equals(0));
+ });
+ test('consumes events', () async {
+ final queue = _countingStream(3);
+ await check(queue).emitsThrough((it) => it.equals(1));
+ await check(queue).emits((it) => it.equals(2));
+ });
+ });
+
+ group('emitsInOrder', () {
+ test('succeeds for happy case', () async {
+ await check(_countingStream(2)).inOrder([
+ (it) => it.emits((it) => it.equals(0)),
+ (it) => it.emits((it) => it.equals(1)),
+ (it) => it.isDone(),
+ ]);
+ });
+ test('reports which condition failed', () async {
+ await check(_countingStream(1)).isRejectedByAsync(
+ (it) => it.inOrder([(it) => it.emits(), (it) => it.emits()]),
+ actual: ['a stream'],
+ which: [
+ 'satisfied 1 conditions then',
+ 'failed to satisfy the condition at index 1',
+ 'because it closed without emitting enough values'
+ ],
+ );
+ });
+ test('nestes the report for deep failures', () async {
+ await check(_countingStream(2)).isRejectedByAsync(
+ (it) => it.inOrder(
+ [(it) => it.emits(), (it) => it.emits((it) => it.equals(2))]),
+ actual: ['a stream'],
+ which: [
+ 'satisfied 1 conditions then',
+ 'failed to satisfy the condition at index 1',
+ 'because it:',
+ ' emits a value that:',
+ ' Actual: <1>',
+ ' Which: are not equal',
+ ],
+ );
+ });
+ test('gets described with the number of conditions', () async {
+ await check(
+ (Subject<StreamQueue<int>> it) => it.inOrder([(_) {}, (_) {}]))
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' satisfies 2 conditions in order']));
+ });
+ test('uses a transaction', () async {
+ final queue = _countingStream(3);
+ await softCheckAsync<StreamQueue<int>>(
+ queue,
+ (it) => it.inOrder([
+ (it) => it.emits((it) => it.equals(0)),
+ (it) => it.emits((it) => it.equals(1)),
+ (it) => it.emits((it) => it.equals(42)),
+ ]));
+ await check(queue).inOrder([
+ (it) => it.emits((it) => it.equals(0)),
+ (it) => it.emits((it) => it.equals(1)),
+ (it) => it.emits((it) => it.equals(2)),
+ (it) => it.isDone(),
+ ]);
+ });
+ test('consumes events', () async {
+ final queue = _countingStream(3);
+ await check(queue).inOrder([(it) => it.emits(), (it) => it.emits()]);
+ await check(queue).emits((it) => it.equals(2));
+ });
+ });
+
+ group('neverEmits', () {
+ test(
+ 'succeeds for a stream that closes without emitting a matching value',
+ () async {
+ await check(_countingStream(5)).neverEmits((it) => it.equals(5));
+ });
+ test('fails for a stream that emits a matching value', () async {
+ await check(_countingStream(6)).isRejectedByAsync(
+ (it) => it.neverEmits((it) => it.equals(5)),
+ actual: ['a stream'],
+ which: ['emitted <5>', 'following 5 other items'],
+ );
+ });
+ test('can be described', () async {
+ await check((Subject<StreamQueue<int>> it) =>
+ it.neverEmits((it) => it.equals(42)))
+ .hasAsyncDescriptionWhich((it) => it.deepEquals([
+ ' never emits a value that:',
+ ' equals <42>',
+ ]));
+ });
+ test('uses a transaction', () async {
+ final queue = _countingStream(2);
+ await softCheckAsync<StreamQueue<int>>(
+ queue, (it) => it.neverEmits((it) => it.equals(1)));
+ await check(queue).inOrder([
+ (it) => it.emits((it) => it.equals(0)),
+ (it) => it.emits((it) => it.equals(1)),
+ (it) => it.isDone(),
+ ]);
+ });
+ });
+
+ group('mayEmit', () {
+ test('succeeds for a stream that emits a matching value', () async {
+ await check(_countingStream(1)).mayEmit((it) => it.equals(0));
+ });
+ test('succeeds for a stream that emits an error', () async {
+ await check(_countingStream(1, errorAt: 0))
+ .mayEmit((it) => it.equals(0));
+ });
+ test('succeeds for a stream that closes', () async {
+ await check(_countingStream(0)).mayEmit((it) => it.equals(42));
+ });
+ test('consumes a matching event', () async {
+ final queue = _countingStream(2);
+ await softCheckAsync<StreamQueue<int>>(
+ queue, (it) => it.mayEmit((it) => it.equals(0)));
+ await check(queue).emits((it) => it.equals(1));
+ });
+ test('does not consume a non-matching event', () async {
+ final queue = _countingStream(2);
+ await softCheckAsync<StreamQueue<int>>(
+ queue, (it) => it.mayEmit((it) => it.equals(1)));
+ await check(queue).emits((it) => it.equals(0));
+ });
+ test('does not consume an error', () async {
+ final queue = _countingStream(1, errorAt: 0);
+ await softCheckAsync<StreamQueue<int>>(
+ queue, (it) => it.mayEmit((it) => it.equals(0)));
+ await check(queue).emitsError<UnimplementedError>(
+ (it) => it.has((e) => e.message, 'message').equals('Error at 1'));
+ });
+ });
+
+ group('mayEmitMultiple', () {
+ test('succeeds for a stream that emits a matching value', () async {
+ await check(_countingStream(1)).mayEmitMultiple((it) => it.equals(0));
+ });
+ test('succeeds for a stream that emits an error', () async {
+ await check(_countingStream(1, errorAt: 0))
+ .mayEmitMultiple((it) => it.equals(0));
+ });
+ test('succeeds for a stream that closes', () async {
+ await check(_countingStream(0)).mayEmitMultiple((it) => it.equals(42));
+ });
+ test('consumes matching events', () async {
+ final queue = _countingStream(3);
+ await softCheckAsync<StreamQueue<int>>(
+ queue, (it) => it.mayEmitMultiple((it) => it.isLessThan(2)));
+ await check(queue).emits((it) => it.equals(2));
+ });
+ test('consumes no events if no events match', () async {
+ final queue = _countingStream(2);
+ await softCheckAsync<StreamQueue<int>>(
+ queue, (it) => it.mayEmitMultiple((it) => it.isLessThan(0)));
+ await check(queue).emits((it) => it.equals(0));
+ });
+ test('does not consume an error', () async {
+ final queue = _countingStream(1, errorAt: 0);
+ await softCheckAsync<StreamQueue<int>>(
+ queue, (it) => it.mayEmitMultiple((it) => it.equals(0)));
+ await check(queue).emitsError<UnimplementedError>(
+ (it) => it.has((e) => e.message, 'message').equals('Error at 1'));
+ });
+ });
+
+ group('isDone', () {
+ test('succeeds for an empty stream', () async {
+ await check(_countingStream(0)).isDone();
+ });
+ test('fails for a stream that emits a value', () async {
+ await check(_countingStream(1)).isRejectedByAsync((it) => it.isDone(),
+ actual: ['a stream'], which: ['emitted an unexpected value: <0>']);
+ });
+ test('fails for a stream that emits an error', () async {
+ final controller = StreamController<void>();
+ controller.addError('sad', StackTrace.fromString('fake trace'));
+ await check(StreamQueue(controller.stream)).isRejectedByAsync(
+ (it) => it.isDone(),
+ actual: ['a stream'],
+ which: ['emitted an unexpected error: \'sad\'', 'fake trace']);
+ });
+ test('uses a transaction', () async {
+ final queue = _countingStream(1);
+ await softCheckAsync<StreamQueue<int>>(queue, (it) => it.isDone());
+ await check(queue).emits((it) => it.equals(0));
+ });
+ test('can be described', () async {
+ await check((Subject<StreamQueue<int>> it) => it.isDone())
+ .hasAsyncDescriptionWhich((it) => it.deepEquals([' is done']));
+ });
+ });
+
+ group('emitsAnyOf', () {
+ test('succeeds for a stream that matches one condition', () async {
+ await check(_countingStream(1)).anyOf([
+ (it) => it.emits((it) => it.equals(42)),
+ (it) => it.emits((it) => it.equals(0))
+ ]);
+ });
+ test('fails for a stream that matches no conditions', () async {
+ await check(_countingStream(0)).isRejectedByAsync(
+ (it) => it.anyOf([
+ (it) => it.emits(),
+ (it) => it.emitsThrough((it) => it.equals(1)),
+ ]),
+ actual: [
+ 'a stream'
+ ],
+ which: [
+ 'failed to satisfy any condition',
+ 'failed the condition at index 0 because it:',
+ ' closed without emitting enough values',
+ 'failed the condition at index 1 because it:',
+ ' ended after emitting 0 elements with none matching',
+ ]);
+ });
+ test('includes nested details for nested failures', () async {
+ await check(_countingStream(1)).isRejectedByAsync(
+ (it) => it.anyOf([
+ (it) => it.emits((it) => it.equals(42)),
+ (it) => it.emitsThrough((it) => it.equals(10)),
+ ]),
+ actual: [
+ 'a stream'
+ ],
+ which: [
+ 'failed to satisfy any condition',
+ 'failed the condition at index 0 because it:',
+ ' emits a value that:',
+ ' Actual: <0>',
+ ' Which: are not equal',
+ 'failed the condition at index 1 because it:',
+ ' ended after emitting 1 elements with none matching',
+ ]);
+ });
+ test('gets described with the number of conditions', () async {
+ await check((Subject<StreamQueue<int>> it) =>
+ it..anyOf([(it) => it.emits(), (it) => it.emits()]))
+ .hasAsyncDescriptionWhich(
+ (it) => it.deepEquals([' satisfies any of 2 conditions']));
+ });
+ test('uses a transaction', () async {
+ final queue = _countingStream(1);
+ await softCheckAsync<StreamQueue<int>>(
+ queue,
+ (it) => it.anyOf([
+ (it) => it.emits((it) => it.equals(10)),
+ (it) => it.emitsThrough((it) => it.equals(42)),
+ ]));
+ await check(queue).emits((it) => it.equals(0));
+ });
+ test('consumes events', () async {
+ final queue = _countingStream(3);
+ await check(queue).anyOf([
+ (it) => it.emits((it) => it.equals(1)),
+ (it) => it.emitsThrough((it) => it.equals(1))
+ ]);
+ await check(queue).emits((it) => it.equals(2));
+ });
+ });
+ });
+
+ group('StreamQueueWrap', () {
+ test('can wrap streams in a queue', () async {
+ await check(Stream.value(1)).withQueue.emits();
+ });
+ });
+}
+
+Future<int> _futureSuccess() => Future.microtask(() => 42);
+
+Future<int> _futureFail() =>
+ Future.error(UnimplementedError(), StackTrace.fromString('fake trace'));
+
+StreamQueue<int> _countingStream(int count, {int? errorAt}) => StreamQueue(
+ Stream.fromIterable(
+ Iterable<int>.generate(count, (index) {
+ if (index == errorAt) {
+ Error.throwWithStackTrace(UnimplementedError('Error at $count'),
+ StackTrace.fromString('fake trace'));
+ }
+ return index;
+ }),
+ ),
+ );
diff --git a/pkgs/checks/test/extensions/collection_equality_test.dart b/pkgs/checks/test/extensions/collection_equality_test.dart
new file mode 100644
index 0000000..28a10dd
--- /dev/null
+++ b/pkgs/checks/test/extensions/collection_equality_test.dart
@@ -0,0 +1,197 @@
+// 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 'package:checks/checks.dart';
+import 'package:checks/src/collection_equality.dart';
+import 'package:test/scaffolding.dart';
+
+void main() {
+ group('deepCollectionEquals', () {
+ test('allows nested collections with equal elements', () {
+ check(deepCollectionEquals([
+ 'a',
+ {'b': 1},
+ {'c', 'd'},
+ [
+ ['e']
+ ],
+ ], [
+ 'a',
+ {'b': 1},
+ {'c', 'd'},
+ [
+ ['e']
+ ],
+ ])).isNull();
+ });
+
+ test('allows collections inside sets', () {
+ check(deepCollectionEquals({
+ {'a': 1}
+ }, {
+ {'a': 1}
+ })).isNull();
+ });
+
+ test('allows collections as Map keys', () {
+ check(deepCollectionEquals([
+ {
+ {'a': 1}: {'b': 2}
+ }
+ ], [
+ {
+ {'a': 1}: {'b': 2}
+ }
+ ])).isNull();
+ });
+
+ test('allows conditions in place of elements in lists', () {
+ check(deepCollectionEquals([
+ 'a',
+ 'b'
+ ], [
+ (Subject<dynamic> it) => it.isA<String>().which((it) => it
+ ..startsWith('a')
+ ..length.isLessThan(2)),
+ (Subject<dynamic> it) => it.isA<String>().startsWith('b')
+ ])).isNull();
+ });
+
+ test('allows conditions in place of values in maps', () {
+ check(deepCollectionEquals([
+ {'a': 'b'}
+ ], [
+ {'a': (Subject<dynamic> it) => it.isA<String>().startsWith('b')}
+ ])).isNull();
+ });
+
+ test('allows conditions in place of elements in sets', () {
+ check(deepCollectionEquals({
+ 'b',
+ 'a'
+ }, {
+ 'a',
+ (Subject<dynamic> it) => it.isA<String>().startsWith('b')
+ })).isNull();
+ });
+
+ test('allows conditions in place of keys in maps', () {
+ check(deepCollectionEquals({
+ 'a': 'b'
+ }, {
+ (Subject<dynamic> it) => it.isA<String>().startsWith('a'): 'b'
+ })).isNull();
+ });
+
+ test('reports non-Set elements', () {
+ check(deepCollectionEquals([
+ ['a']
+ ], [
+ {'a'}
+ ])).isNotNull().deepEquals(['at [<0>] is not a Set']);
+ });
+
+ test('reports long iterables', () {
+ check(deepCollectionEquals([0], [])).isNotNull().deepEquals([
+ 'has more elements than expected',
+ 'expected an iterable with 0 element(s)'
+ ]);
+ });
+
+ test('reports short iterables', () {
+ check(deepCollectionEquals([], [0])).isNotNull().deepEquals([
+ 'has too few elements',
+ 'expected an iterable with at least 1 element(s)'
+ ]);
+ });
+
+ test('reports unequal elements in iterables', () {
+ check(deepCollectionEquals([0], [1]))
+ .isNotNull()
+ .deepEquals(['at [<0>] is <0>', 'which does not equal <1>']);
+ });
+
+ test('reports unmet conditions in iterables', () {
+ check(deepCollectionEquals(
+ [0], [(Subject<dynamic> it) => it.isA<int>().isGreaterThan(0)]))
+ .isNotNull()
+ .deepEquals([
+ 'has an element at [<0>] that:',
+ ' Actual: <0>',
+ ' which is not greater than <0>'
+ ]);
+ });
+
+ test('reports unmet conditions in map values', () {
+ check(deepCollectionEquals({
+ 'a': 'b'
+ }, {
+ 'a': (Subject<dynamic> it) => it.isA<String>().startsWith('a')
+ })).isNotNull().deepEquals([
+ "has an element at ['a'] that:",
+ " Actual: 'b'",
+ " which does not start with 'a'",
+ ]);
+ });
+
+ test('reports unmet conditions in map keys', () {
+ check(deepCollectionEquals({
+ 'b': 'a'
+ }, {
+ (Subject<dynamic> it) => it.isA<String>().startsWith('a'): 'a'
+ })).isNotNull().deepEquals([
+ 'has no entry to match <A value that:',
+ ' is a String',
+ " starts with 'a'>: 'a'",
+ ]);
+ });
+
+ test('maintains paths through maps when the keys are all values', () {
+ check(deepCollectionEquals({
+ 'a': [
+ {'b': 'c'}
+ ]
+ }, {
+ 'a': [
+ {'b': 'd'}
+ ]
+ })).isNotNull().deepEquals([
+ "at ['a'][<0>]['b'] is 'c'",
+ "which does not equal 'd'",
+ ]);
+ });
+
+ test('reports recursive lists', () {
+ var l = <Object>[];
+ l.add(l);
+ check(deepCollectionEquals(l, l))
+ .isNotNull()
+ .deepEquals(['exceeds the depth limit of 1000']);
+ });
+
+ test('reports recursive sets', () {
+ var s = <Object>{};
+ s.add(s);
+ check(deepCollectionEquals(s, s))
+ .isNotNull()
+ .deepEquals(['exceeds the depth limit of 1000']);
+ });
+
+ test('reports maps with recursive keys', () {
+ var m = <Object, Object>{};
+ m[m] = 0;
+ check(deepCollectionEquals(m, m))
+ .isNotNull()
+ .deepEquals(['exceeds the depth limit of 1000']);
+ });
+
+ test('reports maps with recursive values', () {
+ var m = <Object, Object>{};
+ m[0] = m;
+ check(deepCollectionEquals(m, m))
+ .isNotNull()
+ .deepEquals(['exceeds the depth limit of 1000']);
+ });
+ });
+}
diff --git a/pkgs/checks/test/extensions/core_test.dart b/pkgs/checks/test/extensions/core_test.dart
new file mode 100644
index 0000000..2133c36
--- /dev/null
+++ b/pkgs/checks/test/extensions/core_test.dart
@@ -0,0 +1,163 @@
+// Copyright (c) 2022, 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+import '../test_shared.dart';
+
+void main() {
+ group('TypeChecks', () {
+ test('isA', () {
+ check(1).isA<int>();
+
+ check(1).isRejectedBy((it) => it.isA<String>(), which: ['Is a int']);
+ });
+ });
+ group('HasField', () {
+ test('has', () {
+ check(1).has((v) => v.isOdd, 'isOdd').isTrue();
+
+ check(null).isRejectedBy(
+ (it) => it.has((v) {
+ Error.throwWithStackTrace(
+ UnimplementedError(), StackTrace.fromString('fake trace'));
+ }, 'foo').isNotNull(),
+ which: [
+ 'threw while trying to read foo: <UnimplementedError>',
+ 'fake trace'
+ ]);
+ });
+
+ test('which', () {
+ check(true).which((it) => it.isTrue());
+ });
+
+ test('not', () {
+ check(false).not((it) => it.isTrue());
+ check(true).isRejectedBy((it) => it.not((it) => it.isTrue()), which: [
+ 'is a value that: ',
+ ' is true',
+ ]);
+ });
+
+ group('anyOf', () {
+ test('succeeds for happy case', () {
+ check(-10)
+ .anyOf([(it) => it.isGreaterThan(1), (it) => it.isLessThan(-1)]);
+ });
+ test('rejects values that do not satisfy any condition', () {
+ check(0).isRejectedBy(
+ (it) => it.anyOf(
+ [(it) => it.isGreaterThan(1), (it) => it.isLessThan(-1)]),
+ which: ['did not match any condition']);
+ });
+ });
+ });
+
+ group('BoolChecks', () {
+ test('isTrue', () {
+ check(true).isTrue();
+
+ check(false).isRejectedBy((it) => it.isTrue());
+ });
+
+ test('isFalse', () {
+ check(false).isFalse();
+
+ check(true).isRejectedBy((it) => it.isFalse());
+ });
+ });
+
+ group('EqualityChecks', () {
+ test('equals', () {
+ check(1).equals(1);
+
+ check(1).isRejectedBy((it) => it.equals(2), which: ['are not equal']);
+ });
+ test('identical', () {
+ check(1).identicalTo(1);
+
+ check(1)
+ .isRejectedBy((it) => it.identicalTo(2), which: ['is not identical']);
+ });
+ });
+ group('NullabilityChecks', () {
+ test('isNotNull', () {
+ check(1).isNotNull();
+
+ check(null).isRejectedBy((it) => it.isNotNull());
+ });
+ test('isNull', () {
+ check(null).isNull();
+
+ check(1).isRejectedBy((it) => it.isNull());
+ });
+ });
+
+ group('ComparableChecks on Duration', () {
+ group('isGreaterThan', () {
+ test('succeeds for greater', () {
+ check(const Duration(seconds: 10))
+ .isGreaterThan(const Duration(seconds: 1));
+ });
+ test('fails for equal', () {
+ check(const Duration(seconds: 10)).isRejectedBy(
+ (it) => it.isGreaterThan(const Duration(seconds: 10)),
+ which: ['is not greater than <0:00:10.000000>']);
+ });
+ test('fails for less', () {
+ check(const Duration(seconds: 10)).isRejectedBy(
+ (it) => it.isGreaterThan(const Duration(seconds: 50)),
+ which: ['is not greater than <0:00:50.000000>']);
+ });
+ });
+ group('isGreaterOrEqual', () {
+ test('succeeds for greater', () {
+ check(const Duration(seconds: 10))
+ .isGreaterOrEqual(const Duration(seconds: 1));
+ });
+ test('succeeds for equal', () {
+ check(const Duration(seconds: 10))
+ .isGreaterOrEqual(const Duration(seconds: 10));
+ });
+ test('fails for less', () {
+ check(const Duration(seconds: 10)).isRejectedBy(
+ (it) => it.isGreaterOrEqual(const Duration(seconds: 50)),
+ which: ['is not greater than or equal to <0:00:50.000000>']);
+ });
+ });
+ group('isLessThan', () {
+ test('succeeds for less', () {
+ check(const Duration(seconds: 1))
+ .isLessThan(const Duration(seconds: 10));
+ });
+ test('fails for equal', () {
+ check(const Duration(seconds: 10)).isRejectedBy(
+ (it) => it.isLessThan(const Duration(seconds: 10)),
+ which: ['is not less than <0:00:10.000000>']);
+ });
+ test('fails for greater', () {
+ check(const Duration(seconds: 50)).isRejectedBy(
+ (it) => it.isLessThan(const Duration(seconds: 10)),
+ which: ['is not less than <0:00:10.000000>']);
+ });
+ });
+ group('isLessOrEqual', () {
+ test('succeeds for less', () {
+ check(const Duration(seconds: 10))
+ .isLessOrEqual(const Duration(seconds: 50));
+ });
+ test('succeeds for equal', () {
+ check(const Duration(seconds: 10))
+ .isLessOrEqual(const Duration(seconds: 10));
+ });
+ test('fails for greater', () {
+ check(const Duration(seconds: 10)).isRejectedBy(
+ (it) => it.isLessOrEqual(const Duration(seconds: 1)),
+ which: ['is not less than or equal to <0:00:01.000000>']);
+ });
+ });
+ });
+}
diff --git a/pkgs/checks/test/extensions/function_test.dart b/pkgs/checks/test/extensions/function_test.dart
new file mode 100644
index 0000000..034d8fe
--- /dev/null
+++ b/pkgs/checks/test/extensions/function_test.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2022, 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+import '../test_shared.dart';
+
+void main() {
+ group('ThrowsChecks', () {
+ group('throws', () {
+ test('succeeds for happy case', () {
+ check(() => throw StateError('oops!')).throws<StateError>();
+ });
+ test('fails for functions that return normally', () {
+ check(() {}).isRejectedBy((it) => it.throws<StateError>(),
+ actual: ['a function that returned <null>'],
+ which: ['did not throw']);
+ });
+ test('fails for functions that throw the wrong type', () {
+ check(() => throw StateError('oops!')).isRejectedBy(
+ (it) => it.throws<ArgumentError>(),
+ actual: ['a function that threw error <Bad state: oops!>'],
+ which: ['did not throw an ArgumentError'],
+ );
+ });
+ });
+
+ group('returnsNormally', () {
+ test('succeeds for happy case', () {
+ check(() => 1).returnsNormally().equals(1);
+ });
+ test('fails for functions that throw', () {
+ check(() {
+ Error.throwWithStackTrace(
+ StateError('oops!'), StackTrace.fromString('fake trace'));
+ }).isRejectedBy((it) => it.returnsNormally(),
+ actual: ['a function that throws'],
+ which: ['threw <Bad state: oops!>', 'fake trace']);
+ });
+ });
+ });
+}
diff --git a/pkgs/checks/test/extensions/iterable_test.dart b/pkgs/checks/test/extensions/iterable_test.dart
new file mode 100644
index 0000000..dafd84d
--- /dev/null
+++ b/pkgs/checks/test/extensions/iterable_test.dart
@@ -0,0 +1,275 @@
+// Copyright (c) 2022, 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+import '../test_shared.dart';
+
+Iterable<int> get _testIterable => Iterable.generate(2, (i) => i);
+
+void main() {
+ test('length', () {
+ check(_testIterable).length.equals(2);
+ });
+
+ group('first', () {
+ test('succeeds for happy case', () {
+ check(_testIterable).first.equals(0);
+ });
+ test('rejects empty iterable', () {
+ check(<Object>[])
+ .isRejectedBy((it) => it.first.equals(0), which: ['has no elements']);
+ });
+ });
+
+ group('last', () {
+ test('succeeds for happy case', () {
+ check(_testIterable).last.equals(1);
+ });
+ test('rejects empty iterable', () {
+ check(<Object>[])
+ .isRejectedBy((it) => it.last.equals(0), which: ['has no elements']);
+ });
+ });
+
+ group('single', () {
+ test('succeeds for happy case', () {
+ check([42]).single.equals(42);
+ });
+ test('rejects empty iterable', () {
+ check(<Object>[]).isRejectedBy((it) => it.single.equals(0),
+ which: ['has no elements']);
+ });
+ test('rejects iterable with too many elements', () {
+ check(_testIterable).isRejectedBy((it) => it.single.equals(0),
+ which: ['has more than one element']);
+ });
+ });
+
+ test('isEmpty', () {
+ check(<Object>[]).isEmpty();
+ check(_testIterable)
+ .isRejectedBy((it) => it.isEmpty(), which: ['is not empty']);
+ });
+
+ test('isNotEmpty', () {
+ check(_testIterable).isNotEmpty();
+ check(const Iterable<int>.empty())
+ .isRejectedBy((it) => it.isNotEmpty(), which: ['is empty']);
+ });
+
+ test('contains', () {
+ check(_testIterable).contains(0);
+ check(_testIterable)
+ .isRejectedBy((it) => it.contains(2), which: ['does not contain <2>']);
+ });
+ test('any', () {
+ check(_testIterable).any((it) => it.equals(1));
+ check(_testIterable).isRejectedBy((it) => it.any((it) => it.equals(2)),
+ which: ['Contains no matching element']);
+ });
+
+ group('containsInOrder', () {
+ test('succeeds for happy case', () {
+ check([0, 1, 0, 2, 0, 3]).containsInOrder([1, 2, 3]);
+ });
+ test('can use Condition<dynamic>', () {
+ check([0, 1]).containsInOrder(
+ [(Subject<dynamic> it) => it.isA<int>().isGreaterThan(0)]);
+ });
+ test('can use Condition<T>', () {
+ check([0, 1]).containsInOrder([(Subject<int> it) => it.isGreaterThan(0)]);
+ });
+ test('fails for not found elements by equality', () async {
+ check([0]).isRejectedBy((it) => it.containsInOrder([1]), which: [
+ 'did not have an element matching the expectation at index 0 <1>'
+ ]);
+ });
+ test('fails for not found elements by condition', () async {
+ check([0]).isRejectedBy(
+ (it) => it.containsInOrder(
+ [(Subject<dynamic> it) => it.isA<int>().isGreaterThan(0)]),
+ which: [
+ 'did not have an element matching the expectation at index 0 '
+ '<A value that:',
+ ' is a int',
+ ' is greater than <0>>'
+ ]);
+ });
+ test('can be described', () {
+ check((Subject<Iterable> it) => it.containsInOrder([1, 2, 3]))
+ .description
+ .deepEquals([' contains, in order: [1, 2, 3]']);
+ check((Subject<Iterable> it) =>
+ it.containsInOrder([1, (Subject<dynamic> it) => it.equals(2)]))
+ .description
+ .deepEquals([
+ ' contains, in order: [1,',
+ ' <A value that:',
+ ' equals <2>>]'
+ ]);
+ });
+ });
+
+ group('containsMatchingInOrder', () {
+ test('succeeds for happy case', () {
+ check([0, 1, 0, 2, 0, 3]).containsMatchingInOrder([
+ (it) => it.isLessThan(2),
+ (it) => it.isLessThan(3),
+ (it) => it.isLessThan(4),
+ ]);
+ });
+ test('fails for not found elements', () async {
+ check([0]).isRejectedBy(
+ (it) => it.containsMatchingInOrder([(it) => it.isGreaterThan(0)]),
+ which: [
+ 'did not have an element matching the expectation at index 0 '
+ '<A value that:',
+ ' is greater than <0>>'
+ ]);
+ });
+ test('can be described', () {
+ check((Subject<Iterable<int>> it) => it.containsMatchingInOrder([
+ (it) => it.isLessThan(2),
+ (it) => it.isLessThan(3),
+ (it) => it.isLessThan(4),
+ ])).description.deepEquals([
+ ' contains, in order: [<A value that:',
+ ' is less than <2>>,',
+ ' <A value that:',
+ ' is less than <3>>,',
+ ' <A value that:',
+ ' is less than <4>>]',
+ ]);
+ check((Subject<Iterable<int>> it) => it.containsMatchingInOrder(
+ [(it) => it.equals(1), (it) => it.equals(2)]))
+ .description
+ .deepEquals([
+ ' contains, in order: [<A value that:',
+ ' equals <1>>,',
+ ' <A value that:',
+ ' equals <2>>]'
+ ]);
+ });
+ });
+
+ group('containsEqualInOrder', () {
+ test('succeeds for happy case', () {
+ check([0, 1, 0, 2, 0, 3]).containsEqualInOrder([1, 2, 3]);
+ });
+ test('fails for not found elements', () async {
+ check([0]).isRejectedBy((it) => it.containsEqualInOrder([1]), which: [
+ 'did not have an element equal to the expectation at index 0 <1>'
+ ]);
+ });
+ test('can be described', () {
+ check((Subject<Iterable<int>> it) => it.containsEqualInOrder([1, 2, 3]))
+ .description
+ .deepEquals([' contains, in order: [1, 2, 3]']);
+ check((Subject<Iterable<int>> it) => it.containsEqualInOrder([1, 2]))
+ .description
+ .deepEquals([
+ ' contains, in order: [1, 2]',
+ ]);
+ });
+ });
+ group('every', () {
+ test('succeeds for the happy path', () {
+ check(_testIterable).every((it) => it.isGreaterOrEqual(-1));
+ });
+
+ test('includes details of first failing element', () async {
+ check(_testIterable)
+ .isRejectedBy((it) => it.every((it) => it.isLessThan(0)), which: [
+ 'has an element at index 0 that:',
+ ' Actual: <0>',
+ ' Which: is not less than <0>',
+ ]);
+ });
+ });
+
+ group('unorderedEquals', () {
+ test('success for happy case', () {
+ check(_testIterable).unorderedEquals(_testIterable.toList().reversed);
+ });
+
+ test('reports unmatched elements', () {
+ check(_testIterable).isRejectedBy(
+ (it) => it.unorderedEquals(_testIterable.followedBy([42, 100])),
+ which: [
+ 'has no element equal to the expected element at index 2: <42>',
+ 'or 1 other elements'
+ ]);
+ });
+
+ test('reports unexpected elements', () {
+ check(_testIterable.followedBy([42, 100]))
+ .isRejectedBy((it) => it.unorderedEquals(_testIterable), which: [
+ 'has an unexpected element at index 2: <42>',
+ 'and 1 other unexpected elements'
+ ]);
+ });
+ });
+
+ group('unorderedMatches', () {
+ test('success for happy case', () {
+ check(_testIterable).unorderedMatches(
+ _testIterable.toList().reversed.map((i) => (it) => it.equals(i)));
+ });
+
+ test('reports unmatched elements', () {
+ check(_testIterable).isRejectedBy(
+ (it) => it.unorderedMatches(_testIterable
+ .followedBy([42, 100]).map((i) => (it) => it.equals(i))),
+ which: [
+ 'has no element matching the condition at index 2:',
+ ' equals <42>',
+ 'or 1 other conditions'
+ ]);
+ });
+
+ test('reports unexpected elements', () {
+ check(_testIterable.followedBy([42, 100])).isRejectedBy(
+ (it) => it
+ .unorderedMatches(_testIterable.map((i) => (it) => it.equals(i))),
+ which: [
+ 'has an unmatched element at index 2: <42>',
+ 'and 1 other unmatched elements'
+ ]);
+ });
+ });
+
+ group('pairwiseMatches', () {
+ test('succeeds for the happy path', () {
+ check(_testIterable).pairwiseMatches([1, 2],
+ (expected) => (it) => it.isLessThan(expected), 'is less than');
+ });
+ test('fails for mismatched element', () async {
+ check(_testIterable).isRejectedBy(
+ (it) => it.pairwiseMatches([1, 1],
+ (expected) => (it) => it.isLessThan(expected), 'is less than'),
+ which: [
+ 'does not have an element at index 1 that:',
+ ' is less than <1>',
+ 'Actual element at index 1: <1>',
+ 'Which: is not less than <1>'
+ ]);
+ });
+ test('fails for too few elements', () {
+ check(_testIterable).isRejectedBy(
+ (it) => it.pairwiseMatches([1, 2, 3],
+ (expected) => (it) => it.isLessThan(expected), 'is less than'),
+ which: [
+ 'has too few elements, there is no element to match at index 2'
+ ]);
+ });
+ test('fails for too many elements', () {
+ check(_testIterable).isRejectedBy(
+ (it) => it.pairwiseMatches([1],
+ (expected) => (it) => it.isLessThan(expected), 'is less than'),
+ which: ['has too many elements, expected exactly 1']);
+ });
+ });
+}
diff --git a/pkgs/checks/test/extensions/map_test.dart b/pkgs/checks/test/extensions/map_test.dart
new file mode 100644
index 0000000..cb3e3e4
--- /dev/null
+++ b/pkgs/checks/test/extensions/map_test.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2022, 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+import '../test_shared.dart';
+
+const _testMap = {
+ 'a': 1,
+ 'b': 2,
+};
+
+void main() {
+ test('length', () {
+ check(_testMap).length.equals(2);
+ });
+ test('entries', () {
+ check(_testMap).entries.any(
+ (it) => it
+ ..has((p0) => p0.key, 'key').equals('a')
+ ..has((p0) => p0.value, 'value').equals(1),
+ );
+ });
+ test('keys', () {
+ check(_testMap).keys.contains('a');
+ });
+ test('values', () {
+ check(_testMap).values.contains(1);
+ });
+
+ group('operator []', () {
+ test('succeeds for a key that exists', () {
+ check(_testMap)['a'].equals(1);
+ });
+ test('fails for a missing key', () {
+ check(_testMap).isRejectedBy((it) => it['z'],
+ which: ["does not contain the key 'z'"]);
+ });
+ test('can be described', () {
+ check((Subject<Map<String, Object>> it) => it['some\nlong\nkey'])
+ .description
+ .deepEquals([
+ " contains a value for 'some",
+ ' long',
+ " key'",
+ ]);
+ check((Subject<Map<String, Object>> it) =>
+ it['some\nlong\nkey'].equals(1)).description.deepEquals([
+ " contains a value for 'some",
+ ' long',
+ " key' that:",
+ ' equals <1>',
+ ]);
+ });
+ });
+ test('isEmpty', () {
+ check(<String, int>{}).isEmpty();
+ check(_testMap).isRejectedBy((it) => it.isEmpty(), which: ['is not empty']);
+ });
+ test('isNotEmpty', () {
+ check(_testMap).isNotEmpty();
+ check(<Object, Object>{})
+ .isRejectedBy((it) => it.isNotEmpty(), which: ['is not empty']);
+ });
+ group('containsKey', () {
+ test('succeeds for a key that exists', () {
+ check(_testMap).containsKey('a');
+ });
+ test('fails for a missing key', () {
+ check(_testMap).isRejectedBy(
+ (it) => it.containsKey('c'),
+ which: ["does not contain key 'c'"],
+ );
+ });
+ test('can be described', () {
+ check((Subject<Map<String, Object>> it) =>
+ it.containsKey('some\nlong\nkey')).description.deepEquals([
+ " contains key 'some",
+ ' long',
+ " key'",
+ ]);
+ });
+ });
+ test('containsKeyThat', () {
+ check(_testMap).containsKeyThat((it) => it.equals('a'));
+ check(_testMap).isRejectedBy(
+ (it) => it.containsKeyThat((it) => it.equals('c')),
+ which: ['Contains no matching key'],
+ );
+ });
+ group('containsValue', () {
+ test('succeeds for happy case', () {
+ check(_testMap).containsValue(1);
+ });
+ test('fails for missing value', () {
+ check(_testMap).isRejectedBy(
+ (it) => it.containsValue(3),
+ which: ['does not contain value <3>'],
+ );
+ });
+ test('can be described', () {
+ check((Subject<Map<String, String>> it) =>
+ it.containsValue('some\nlong\nkey')).description.deepEquals([
+ " contains value 'some",
+ ' long',
+ " key'",
+ ]);
+ });
+ });
+ test('containsValueThat', () {
+ check(_testMap).containsValueThat((it) => it.equals(1));
+ check(_testMap).isRejectedBy(
+ (it) => it.containsValueThat((it) => it.equals(3)),
+ which: ['Contains no matching value'],
+ );
+ });
+}
diff --git a/pkgs/checks/test/extensions/math_test.dart b/pkgs/checks/test/extensions/math_test.dart
new file mode 100644
index 0000000..0f346d3
--- /dev/null
+++ b/pkgs/checks/test/extensions/math_test.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2022, 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+import '../test_shared.dart';
+
+void main() {
+ group('num checks', () {
+ group('isNaN', () {
+ test('succeeds for happy case', () {
+ check(double.nan).isNaN();
+ });
+ test('fails for ints', () {
+ check(42).isRejectedBy((it) => it.isNaN(), which: ['is a number']);
+ });
+ test('fails for numeric doubles', () {
+ check(42.1).isRejectedBy((it) => it.isNaN(), which: ['is a number']);
+ });
+ });
+
+ group('isNotNan', () {
+ test('succeeds for ints', () {
+ check(42).isNotNaN();
+ });
+ test('succeeds numeric doubles', () {
+ check(42.1).isNotNaN();
+ });
+ test('fails for NaN', () {
+ check(double.nan).isRejectedBy((it) => it.isNotNaN(),
+ which: ['is not a number (NaN)']);
+ });
+ });
+ group('isNegative', () {
+ test('succeeds for negative ints', () {
+ check(-1).isNegative();
+ });
+ test('succeeds for -0.0', () {
+ check(-0.0).isNegative();
+ });
+ test('fails for zero', () {
+ check(0)
+ .isRejectedBy((it) => it.isNegative(), which: ['is not negative']);
+ });
+ });
+ group('isNotNegative', () {
+ test('succeeds for positive ints', () {
+ check(1).isNotNegative();
+ });
+ test('succeeds for 0', () {
+ check(0).isNotNegative();
+ });
+ test('fails for -0.0', () {
+ check(-0.0)
+ .isRejectedBy((it) => it.isNotNegative(), which: ['is negative']);
+ });
+ test('fails for negative numbers', () {
+ check(-1)
+ .isRejectedBy((it) => it.isNotNegative(), which: ['is negative']);
+ });
+ });
+
+ group('isFinite', () {
+ test('succeeds for finite numbers', () {
+ check(1).isFinite();
+ });
+ test('fails for NaN', () {
+ check(double.nan)
+ .isRejectedBy((it) => it.isFinite(), which: ['is not finite']);
+ });
+ test('fails for infinity', () {
+ check(double.infinity)
+ .isRejectedBy((it) => it.isFinite(), which: ['is not finite']);
+ });
+ test('fails for negative infinity', () {
+ check(double.negativeInfinity)
+ .isRejectedBy((it) => it.isFinite(), which: ['is not finite']);
+ });
+ });
+ group('isNotFinite', () {
+ test('succeeds for infinity', () {
+ check(double.infinity).isNotFinite();
+ });
+ test('succeeds for negative infinity', () {
+ check(double.negativeInfinity).isNotFinite();
+ });
+ test('succeeds for NaN', () {
+ check(double.nan).isNotFinite();
+ });
+ test('fails for finite numbers', () {
+ check(1).isRejectedBy((it) => it.isNotFinite(), which: ['is finite']);
+ });
+ });
+ group('isInfinite', () {
+ test('succeeds for infinity', () {
+ check(double.infinity).isInfinite();
+ });
+ test('succeeds for negative infinity', () {
+ check(double.negativeInfinity).isInfinite();
+ });
+ test('fails for NaN', () {
+ check(double.nan)
+ .isRejectedBy((it) => it.isInfinite(), which: ['is not infinite']);
+ });
+ test('fails for finite numbers', () {
+ check(1)
+ .isRejectedBy((it) => it.isInfinite(), which: ['is not infinite']);
+ });
+ });
+
+ group('isNotInfinite', () {
+ test('succeeds for finite numbers', () {
+ check(1).isNotInfinite();
+ });
+ test('succeeds for NaN', () {
+ check(double.nan).isNotInfinite();
+ });
+ test('fails for infinity', () {
+ check(double.infinity)
+ .isRejectedBy((it) => it.isNotInfinite(), which: ['is infinite']);
+ });
+ test('fails for negative infinity', () {
+ check(double.negativeInfinity)
+ .isRejectedBy((it) => it.isNotInfinite(), which: ['is infinite']);
+ });
+ });
+ group('closeTo', () {
+ test('succeeds for equal numbers', () {
+ check(1).isCloseTo(1, 1);
+ });
+ test('succeeds at less than delta away', () {
+ check(1).isCloseTo(2, 2);
+ });
+ test('succeeds at exactly delta away', () {
+ check(1).isCloseTo(2, 1);
+ });
+ test('fails for low values', () {
+ check(1).isRejectedBy((it) => it.isCloseTo(3, 1),
+ which: ['differs by <2>']);
+ });
+ test('fails for high values', () {
+ check(5).isRejectedBy((it) => it.isCloseTo(3, 1),
+ which: ['differs by <2>']);
+ });
+ });
+ });
+}
diff --git a/pkgs/checks/test/extensions/string_test.dart b/pkgs/checks/test/extensions/string_test.dart
new file mode 100644
index 0000000..2b651f1
--- /dev/null
+++ b/pkgs/checks/test/extensions/string_test.dart
@@ -0,0 +1,200 @@
+// Copyright (c) 2022, 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+import '../test_shared.dart';
+
+void main() {
+ group('StringChecks', () {
+ test('contains', () {
+ check('bob').contains('bo');
+ check('bob').isRejectedBy((it) => it.contains('kayleb'),
+ which: ["Does not contain 'kayleb'"]);
+ });
+ test('length', () {
+ check('bob').length.equals(3);
+ });
+ test('isEmpty', () {
+ check('').isEmpty();
+ check('bob').isRejectedBy((it) => it.isEmpty(), which: ['is not empty']);
+ });
+ test('isNotEmpty', () {
+ check('bob').isNotEmpty();
+ check('').isRejectedBy((it) => it.isNotEmpty(), which: ['is empty']);
+ });
+ test('startsWith', () {
+ check('bob').startsWith('bo');
+ check('bob').isRejectedBy((it) => it.startsWith('kayleb'),
+ which: ["does not start with 'kayleb'"]);
+ });
+ test('endsWith', () {
+ check('bob').endsWith('ob');
+ check('bob').isRejectedBy((it) => it.endsWith('kayleb'),
+ which: ["does not end with 'kayleb'"]);
+ });
+
+ group('matches', () {
+ test('succeeds for strings that match a regex', () {
+ check('123').matchesPattern(RegExp(r'\d\d\d'));
+ });
+ test('succeeds for strings that match a string pattern', () {
+ check(r'\d').matchesPattern(r'\d');
+ });
+ test('fails for non-matching regex', () {
+ check('abc').isRejectedBy((it) => it.matchesPattern(RegExp(r'\d\d\d')),
+ which: [r'does not match <RegExp: pattern=\d\d\d flags=>']);
+ });
+ test('fails for non-matching string pattern', () {
+ // A string is _not_ converted to a regex, string patterns must match
+ // directly.
+ check('123').isRejectedBy((it) => it.matchesPattern(r'\d\d\d'),
+ which: [r"does not match '\\d\\d\\d'"]);
+ });
+ test('can be described', () {
+ check((Subject<String> it) => it.matchesPattern(RegExp(r'\d\d\d')))
+ .description
+ .deepEquals([r' matches <RegExp: pattern=\d\d\d flags=>']);
+ check((Subject<String> it) => it.matchesPattern('abc'))
+ .description
+ .deepEquals([r" matches 'abc'"]);
+ });
+ });
+
+ group('containsInOrder', () {
+ test('happy case', () {
+ check('foo bar baz').containsInOrder(['foo', 'baz']);
+ });
+ test('reports when first substring is missing', () {
+ check('baz').isRejectedBy((it) => it.containsInOrder(['foo', 'baz']),
+ which: ['does not have a match for the substring \'foo\'']);
+ });
+ test('reports when substring is missing following a match', () {
+ check('foo bar')
+ .isRejectedBy((it) => it.containsInOrder(['foo', 'baz']), which: [
+ 'does not have a match for the substring \'baz\'',
+ 'following the other matches up to character 3'
+ ]);
+ });
+ });
+
+ group('equals', () {
+ test('succeeeds for happy case', () {
+ check('foo').equals('foo');
+ });
+ test('succeeeds for equal empty strings', () {
+ check('').equals('');
+ });
+ test('reports extra characters for long string', () {
+ check('foobar').isRejectedBy((it) => it.equals('foo'),
+ which: ['is too long with unexpected trailing characters:', 'bar']);
+ });
+ test('reports extra characters for long string against empty', () {
+ check('foo').isRejectedBy((it) => it.equals(''),
+ which: ['is not the empty string']);
+ });
+ test('reports truncated extra characters for very long string', () {
+ check('foobar baz more stuff').isRejectedBy((it) => it.equals('foo'),
+ which: [
+ 'is too long with unexpected trailing characters:',
+ 'bar baz mo ...'
+ ]);
+ });
+ test('reports missing characters for short string', () {
+ check('foo').isRejectedBy((it) => it.equals('foobar'),
+ which: ['is too short with missing trailing characters:', 'bar']);
+ });
+ test('reports missing characters for empty string', () {
+ check('').isRejectedBy((it) => it.equals('foo bar baz'),
+ actual: ['an empty string'],
+ which: ['is missing all expected characters:', 'foo bar ba ...']);
+ });
+ test('reports truncated missing characters for very short string', () {
+ check('foo').isRejectedBy((it) => it.equals('foobar baz more stuff'),
+ which: [
+ 'is too short with missing trailing characters:',
+ 'bar baz mo ...'
+ ]);
+ });
+ test('reports index of different character', () {
+ check('hit').isRejectedBy((it) => it.equals('hat'), which: [
+ 'differs at offset 1:',
+ 'hat',
+ 'hit',
+ ' ^',
+ ]);
+ });
+ test('reports truncated index of different character in large string',
+ () {
+ check('blah blah blah hit blah blah blah').isRejectedBy(
+ (it) => it.equals('blah blah blah hat blah blah blah'),
+ which: [
+ 'differs at offset 16:',
+ '... lah blah hat blah bl ...',
+ '... lah blah hit blah bl ...',
+ ' ^',
+ ]);
+ });
+ });
+
+ group('equalsIgnoringCase', () {
+ test('succeeeds for happy case', () {
+ check('FOO').equalsIgnoringCase('foo');
+ check('foo').equalsIgnoringCase('FOO');
+ });
+ test('reports original extra characters for long string', () {
+ check('FOOBAR').isRejectedBy((it) => it.equalsIgnoringCase('foo'),
+ which: ['is too long with unexpected trailing characters:', 'BAR']);
+ });
+ test('reports original missing characters for short string', () {
+ check('FOO').isRejectedBy((it) => it.equalsIgnoringCase('fooBAR'),
+ which: ['is too short with missing trailing characters:', 'BAR']);
+ });
+ test('reports index of different character with original characters', () {
+ check('HiT').isRejectedBy((it) => it.equalsIgnoringCase('hAt'), which: [
+ 'differs at offset 1:',
+ 'hAt',
+ 'HiT',
+ ' ^',
+ ]);
+ });
+ });
+
+ group('equalsIgnoringWhitespace', () {
+ test('allows differing internal whitespace', () {
+ check('foo \t\n bar').equalsIgnoringWhitespace('foo bar');
+ });
+ test('allows extra leading/trailing whitespace', () {
+ check(' foo ').equalsIgnoringWhitespace('foo');
+ });
+ test('allows missing leading/trailing whitespace', () {
+ check('foo').equalsIgnoringWhitespace(' foo ');
+ });
+ test('reports original extra characters for long string', () {
+ check('foo \t bar \n baz').isRejectedBy(
+ (it) => it.equalsIgnoringWhitespace('foo bar'),
+ which: [
+ 'is too long with unexpected trailing characters:',
+ ' baz'
+ ]);
+ });
+ test('reports original missing characters for short string', () {
+ check('foo bar').isRejectedBy(
+ (it) => it.equalsIgnoringWhitespace('foo bar baz'),
+ which: ['is too short with missing trailing characters:', ' baz']);
+ });
+ test('reports index of different character with original characters', () {
+ check('x hit x').isRejectedBy(
+ (it) => it.equalsIgnoringWhitespace('x hat x'),
+ which: [
+ 'differs at offset 3:',
+ 'x hat x',
+ 'x hit x',
+ ' ^',
+ ]);
+ });
+ });
+ });
+}
diff --git a/pkgs/checks/test/failure_message_test.dart b/pkgs/checks/test/failure_message_test.dart
new file mode 100644
index 0000000..ee5401c
--- /dev/null
+++ b/pkgs/checks/test/failure_message_test.dart
@@ -0,0 +1,53 @@
+import 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+import 'package:test_api/hooks.dart' show TestFailure;
+
+void main() {
+ group('failures', () {
+ test('includes expected, actual, and which', () {
+ check(() {
+ check(1).isGreaterThan(2);
+ }).throwsFailure().equals('''
+Expected: a int that:
+ is greater than <2>
+Actual: <1>
+Which: is not greater than <2>''');
+ });
+
+ test('includes matching portions of actual', () {
+ check(() {
+ check(<dynamic>[]).length.equals(1);
+ }).throwsFailure().equals('''
+Expected: a List<dynamic> that:
+ has length that:
+ equals <1>
+Actual: a List<dynamic> that:
+ has length that:
+ Actual: <0>
+ Which: are not equal''');
+ });
+
+ test('include a reason when provided', () {
+ check(() {
+ check(because: 'Some reason', 1).isGreaterThan(2);
+ }).throwsFailure().endsWith('Reason: Some reason');
+ });
+
+ test('retain type label following isNotNull', () {
+ check(() {
+ check<int?>(1).isNotNull().isGreaterThan(2);
+ }).throwsFailure().startsWith('Expected: a int? that:\n');
+ });
+
+ test('retain reason following isNotNull', () {
+ check(() {
+ check<int?>(because: 'Some reason', 1).isNotNull().isGreaterThan(2);
+ }).throwsFailure().endsWith('Reason: Some reason');
+ });
+ });
+}
+
+extension on Subject<void Function()> {
+ Subject<String> throwsFailure() =>
+ throws<TestFailure>().has((f) => f.message, 'message').isNotNull();
+}
diff --git a/pkgs/checks/test/soft_check_test.dart b/pkgs/checks/test/soft_check_test.dart
new file mode 100644
index 0000000..d919b0d
--- /dev/null
+++ b/pkgs/checks/test/soft_check_test.dart
@@ -0,0 +1,30 @@
+// 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 'package:checks/checks.dart';
+import 'package:test/scaffolding.dart';
+
+import 'test_shared.dart';
+
+void main() {
+ group('softCheck', () {
+ test('returns the first failure', () {
+ check(0).isRejectedBy(
+ (it) => it
+ ..isGreaterThan(1)
+ ..isGreaterThan(2),
+ which: ['is not greater than <1>']);
+ });
+ });
+ group('softCheckAsync', () {
+ test('returns the first failure', () async {
+ await check(Future.value(0)).isRejectedByAsync(
+ (it) => it
+ ..completes((it) => it.isGreaterThan(1))
+ ..completes((it) => it.isGreaterThan(2)),
+ actual: ['<0>'],
+ which: ['is not greater than <1>']);
+ });
+ });
+}
diff --git a/pkgs/checks/test/test_shared.dart b/pkgs/checks/test/test_shared.dart
new file mode 100644
index 0000000..87401ca
--- /dev/null
+++ b/pkgs/checks/test/test_shared.dart
@@ -0,0 +1,92 @@
+// Copyright (c) 2022, 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 'package:checks/checks.dart';
+import 'package:checks/context.dart';
+
+extension RejectionChecks<T> on Subject<T> {
+ void isRejectedBy(Condition<T> condition,
+ {Iterable<String>? actual, Iterable<String>? which}) {
+ late T actualValue;
+ var didRunCallback = false;
+ final rejection = context.nest<Rejection>(
+ () => ['does not meet a condition with a Rejection'], (value) {
+ actualValue = value;
+ didRunCallback = true;
+ final failure = softCheck(value, condition);
+ if (failure == null) {
+ return Extracted.rejection(which: [
+ 'was accepted by the condition checking:',
+ ...describe(condition)
+ ]);
+ }
+ return Extracted.value(failure.rejection);
+ });
+ if (didRunCallback) {
+ rejection
+ .has((r) => r.actual, 'actual')
+ .deepEquals(actual ?? literal(actualValue));
+ } else {
+ rejection
+ .has((r) => r.actual, 'actual')
+ .context
+ .expect(() => ['is left default'], (_) => null);
+ }
+ if (which == null) {
+ rejection.has((r) => r.which, 'which').isNull();
+ } else {
+ rejection.has((r) => r.which, 'which').isNotNull().deepEquals(which);
+ }
+ }
+
+ Future<void> isRejectedByAsync(Condition<T> condition,
+ {Iterable<String>? actual, Iterable<String>? which}) async {
+ late T actualValue;
+ var didRunCallback = false;
+ await context.nestAsync<Rejection>(
+ () => ['does not meet an async condition with a Rejection'],
+ (value) async {
+ actualValue = value;
+ didRunCallback = true;
+ final failure = await softCheckAsync(value, condition);
+ if (failure == null) {
+ return Extracted.rejection(which: [
+ 'was accepted by the condition checking:',
+ ...await describeAsync(condition)
+ ]);
+ }
+ return Extracted.value(failure.rejection);
+ }, (rejection) {
+ if (didRunCallback) {
+ rejection
+ .has((r) => r.actual, 'actual')
+ .deepEquals(actual ?? literal(actualValue));
+ } else {
+ rejection
+ .has((r) => r.actual, 'actual')
+ .context
+ .expect(() => ['is left default'], (_) => null);
+ }
+ if (which == null) {
+ rejection.has((r) => r.which, 'which').isNull();
+ } else {
+ rejection.has((r) => r.which, 'which').isNotNull().deepEquals(which);
+ }
+ });
+ }
+}
+
+extension ConditionChecks<T> on Subject<Condition<T>> {
+ Subject<Iterable<String>> get description =>
+ has((c) => describe<T>(c), 'description');
+ Future<void> hasAsyncDescriptionWhich(
+ Condition<Iterable<String>> descriptionCondition) =>
+ context.nestAsync(
+ () => ['has description'],
+ (condition) async =>
+ Extracted.value(await describeAsync<T>(condition)),
+ descriptionCondition);
+}
diff --git a/pkgs/fake_async/.gitignore b/pkgs/fake_async/.gitignore
new file mode 100644
index 0000000..63fe85d
--- /dev/null
+++ b/pkgs/fake_async/.gitignore
@@ -0,0 +1,5 @@
+.packages
+.pub/
+.dart_tool/
+build/
+pubspec.lock
diff --git a/pkgs/fake_async/AUTHORS b/pkgs/fake_async/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/pkgs/fake_async/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
diff --git a/pkgs/fake_async/CHANGELOG.md b/pkgs/fake_async/CHANGELOG.md
new file mode 100644
index 0000000..0f964ee
--- /dev/null
+++ b/pkgs/fake_async/CHANGELOG.md
@@ -0,0 +1,102 @@
+## 1.3.2
+
+* Require Dart 3.3
+* Fix bug where a `flushTimers` or `elapse` call from within
+ the callback of a periodic timer would immediately invoke
+ the same timer.
+* Move to `dart-lang/test` monorepo.
+* Require Dart 3.5.
+
+## 1.3.1
+
+* Populate the pubspec `repository` field.
+
+## 1.3.0
+
+* `FakeTimer.tick` will return a value instead of throwing.
+* `FakeAsync.includeTimerStackTrace` allows controlling whether timers created
+ with a FakeAsync will include a creation Stack Trace.
+
+## 1.2.0
+
+* Stable release for null safety.
+
+## 1.2.0-nullsafety.3
+
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.2.0-nullsafety.2
+
+* Allow prerelease versions of the 2.12 sdk.
+
+## 1.2.0-nullsafety.1
+
+* Allow 2.10 stable and 2.11.0 dev SDK versions.
+
+## 1.2.0-nullsafety
+
+Pre-release for the null safety migration of this package.
+
+Note that `1.2.0` may not be the final stable null safety release version,
+we reserve the right to release it as a `2.0.0` breaking change.
+
+This release will be pinned to only allow pre-release sdk versions starting
+from `2.10.0-0`.
+
+## 1.1.0
+
+* Exposed the `FakeTimer` class as a public class.
+* Added `FakeAsync.pendingTimers` which gives access to all pending timers at
+ the time of the call.
+
+## 1.0.2
+
+* Update min SDK to 2.2.0
+
+## 1.0.1
+
+* Update to lowercase Dart core library constants.
+* Fix use of deprecated `isInstanceOf` matcher.
+
+## 1.0.0
+
+This release contains the `FakeAsync` class that was defined in [`quiver`][].
+It's backwards-compatible with both the `quiver` version *and* the old version
+of the `fake_async` package.
+
+[`quiver`]: https://pub.dev/packages/quiver
+
+### New Features
+
+* A top-level `fakeAsync()` function was added that encapsulates
+ `new FakeAsync().run(...)`.
+
+### New Features Relative to `quiver`
+
+* `FakeAsync.elapsed` returns the total amount of fake time elapsed since the
+ `FakeAsync` instance was created.
+
+* `new FakeAsync()` now takes an `initialTime` argument that sets the default
+ time for clocks created with `FakeAsync.getClock()`, and for the `clock`
+ package's top-level `clock` variable.
+
+### New Features Relative to `fake_async` 0.1
+
+* `FakeAsync.periodicTimerCount`, `FakeAsync.nonPeriodicTimerCount`, and
+ `FakeAsync.microtaskCount` provide visibility into the events scheduled within
+ `FakeAsync.run()`.
+
+* `FakeAsync.getClock()` provides access to fully-featured `Clock` objects based
+ on `FakeAsync`'s elapsed time.
+
+* `FakeAsync.flushMicrotasks()` empties the microtask queue without elapsing any
+ time or running any timers.
+
+* `FakeAsync.flushTimers()` runs all microtasks and timers until there are no
+ more scheduled.
+
+## 0.1.2
+
+* Integrate with the clock package.
+
diff --git a/pkgs/fake_async/LICENSE b/pkgs/fake_async/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/pkgs/fake_async/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/pkgs/fake_async/README.md b/pkgs/fake_async/README.md
new file mode 100644
index 0000000..c7bb40b
--- /dev/null
+++ b/pkgs/fake_async/README.md
@@ -0,0 +1,51 @@
+[](https://pub.dev/packages/fake_async)
+[](https://pub.dev/packages/fake_async/publisher)
+
+This package provides a [`FakeAsync`][] class, which makes it easy to
+deterministically test code that uses asynchronous features like `Future`s,
+`Stream`s, `Timer`s, and microtasks. It creates an environment in which the user
+can explicitly control Dart's notion of the "current time". When the time is
+advanced, `FakeAsync` fires all asynchronous events that are scheduled for that
+time period without actually needing the test to wait for real time to elapse.
+
+[`FakeAsync`]: https://www.dartdocs.org/documentation/fake_async/latest/fake_async/FakeAsync-class.html
+
+For example:
+
+```dart
+import 'dart:async';
+
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test("Future.timeout() throws an error once the timeout is up", () {
+ // Any code run within [fakeAsync] is run within the context of the
+ // [FakeAsync] object passed to the callback.
+ fakeAsync((async) {
+ // All asynchronous features that rely on timing are automatically
+ // controlled by [fakeAsync].
+ expect(Completer().future.timeout(Duration(seconds: 5)),
+ throwsA(isA<TimeoutException>()));
+
+ // This will cause the timeout above to fire immediately, without waiting
+ // 5 seconds of real time.
+ async.elapse(Duration(seconds: 5));
+ });
+ });
+}
+```
+
+## Integration With `clock`
+
+`FakeAsync` can't control the time reported by [`DateTime.now()`][] or by
+the [`Stopwatch`][] class, since they're not part of `dart:async`. However, if
+you create them using the [`clock`][] package's [`clock.now()`][] or
+[`clock.stopwatch()`][] functions, `FakeAsync` will automatically override
+them to use the same notion of time as `dart:async` classes.
+
+[`DateTime.now()`]: https://api.dart.dev/stable/dart-core/DateTime/DateTime.now.html
+[`Stopwatch`]: https://api.dart.dev/stable/dart-core/Stopwatch-class.html
+[`clock`]: https://pub.dev/packages/clock
+[`clock.now()`]: https://pub.dev/documentation/clock/latest/clock/Clock/now.html
+[`clock.stopwatch()`]: https://pub.dev/documentation/clock/latest/clock/Clock/stopwatch.html
diff --git a/pkgs/fake_async/analysis_options.yaml b/pkgs/fake_async/analysis_options.yaml
new file mode 100644
index 0000000..4e1b8e8
--- /dev/null
+++ b/pkgs/fake_async/analysis_options.yaml
@@ -0,0 +1,21 @@
+include: package:dart_flutter_team_lints/analysis_options.yaml
+analyzer:
+ language:
+ strict-casts: true
+
+linter:
+ rules:
+ - avoid_bool_literals_in_conditional_expressions
+ - avoid_classes_with_only_static_members
+ - avoid_returning_this
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - cascade_invocations
+ - comment_references
+ - join_return_with_assignment
+ - literal_only_boolean_expressions
+ - no_adjacent_strings_in_list
+ - prefer_const_constructors
+ - prefer_final_locals
+ - test_types_in_equals
+ - unnecessary_await_in_return
diff --git a/pkgs/fake_async/lib/fake_async.dart b/pkgs/fake_async/lib/fake_async.dart
new file mode 100644
index 0000000..b4eea85
--- /dev/null
+++ b/pkgs/fake_async/lib/fake_async.dart
@@ -0,0 +1,332 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:clock/clock.dart';
+import 'package:collection/collection.dart';
+
+/// The type of a microtask callback.
+typedef _Microtask = void Function();
+
+/// Runs [callback] in a [Zone] where all asynchrony is controlled by an
+/// instance of [FakeAsync].
+///
+/// All [Future]s, [Stream]s, [Timer]s, microtasks, and other time-based
+/// asynchronous features used within [callback] are controlled by calls to
+/// [FakeAsync.elapse] rather than the passing of real time.
+///
+/// The [`clock`][] property will be set to a clock that reports the fake
+/// elapsed time. By default, it starts at the time [fakeAsync] was created
+/// (according to [`clock.now()`][]), but this can be controlled by passing
+/// [initialTime].
+///
+/// [`clock`]: https://www.dartdocs.org/documentation/clock/latest/clock/clock.html
+/// [`clock.now()`]: https://www.dartdocs.org/documentation/clock/latest/clock/Clock/now.html
+///
+/// Returns the result of [callback].
+T fakeAsync<T>(T Function(FakeAsync async) callback, {DateTime? initialTime}) =>
+ FakeAsync(initialTime: initialTime).run(callback);
+
+/// A class that mocks out the passage of time within a [Zone].
+///
+/// Test code can be passed as a callback to [run], which causes it to be run in
+/// a [Zone] which fakes timer and microtask creation, such that they are run
+/// during calls to [elapse] which simulates the asynchronous passage of time.
+///
+/// The synchronous passage of time (as from blocking or expensive calls) can
+/// also be simulated using [elapseBlocking].
+class FakeAsync {
+ /// The value of [clock] within [run].
+ late final Clock _clock;
+
+ /// The amount of fake time that's elapsed since this [FakeAsync] was
+ /// created.
+ Duration get elapsed => _elapsed;
+ var _elapsed = Duration.zero;
+
+ /// Whether Timers created by this FakeAsync will include a creation stack
+ /// trace in [FakeAsync.pendingTimersDebugString].
+ final bool includeTimerStackTrace;
+
+ /// The fake time at which the current call to [elapse] will finish running.
+ ///
+ /// This is `null` if there's no current call to [elapse].
+ Duration? _elapsingTo;
+
+ /// Tasks that are scheduled to run when fake time progresses.
+ final _microtasks = Queue<_Microtask>();
+
+ /// All timers created within [run].
+ final _timers = <FakeTimer>{};
+
+ /// All the current pending timers.
+ List<FakeTimer> get pendingTimers => _timers.toList(growable: false);
+
+ /// The debug strings for all the current pending timers.
+ List<String> get pendingTimersDebugString =>
+ pendingTimers.map((timer) => timer.debugString).toList(growable: false);
+
+ /// The number of active periodic timers created within a call to [run] or
+ /// [fakeAsync].
+ int get periodicTimerCount =>
+ _timers.where((timer) => timer.isPeriodic).length;
+
+ /// The number of active non-periodic timers created within a call to [run] or
+ /// [fakeAsync].
+ int get nonPeriodicTimerCount =>
+ _timers.where((timer) => !timer.isPeriodic).length;
+
+ /// The number of pending microtasks scheduled within a call to [run] or
+ /// [fakeAsync].
+ int get microtaskCount => _microtasks.length;
+
+ /// Creates a [FakeAsync].
+ ///
+ /// Within [run], the [`clock`][] property will start at [initialTime] and
+ /// move forward as fake time elapses.
+ ///
+ /// [`clock`]: https://www.dartdocs.org/documentation/clock/latest/clock/clock.html
+ ///
+ /// Note: it's usually more convenient to use [fakeAsync] rather than creating
+ /// a [FakeAsync] object and calling [run] manually.
+ FakeAsync({DateTime? initialTime, this.includeTimerStackTrace = true}) {
+ final nonNullInitialTime = initialTime ?? clock.now();
+ _clock = Clock(() => nonNullInitialTime.add(elapsed));
+ }
+
+ /// Returns a fake [Clock] whose time can is elapsed by calls to [elapse] and
+ /// [elapseBlocking].
+ ///
+ /// The returned clock starts at [initialTime] plus the fake time that's
+ /// already been elapsed. Further calls to [elapse] and [elapseBlocking] will
+ /// advance the clock as well.
+ ///
+ /// Note that it's usually easier to use the top-level [`clock`][] property.
+ /// Only call this function if you want a different [initialTime] than the
+ /// default.
+ ///
+ /// [`clock`]: https://www.dartdocs.org/documentation/clock/latest/clock/clock.html
+ Clock getClock(DateTime initialTime) =>
+ Clock(() => initialTime.add(_elapsed));
+
+ /// Simulates the asynchronous passage of time.
+ ///
+ /// Throws an [ArgumentError] if [duration] is negative. Throws a [StateError]
+ /// if a previous call to [elapse] has not yet completed.
+ ///
+ /// Any timers created within [run] or [fakeAsync] will fire if their time is
+ /// within [duration]. The microtask queue is processed before and after each
+ /// timer fires.
+ void elapse(Duration duration) {
+ if (duration.inMicroseconds < 0) {
+ throw ArgumentError.value(duration, 'duration', 'may not be negative');
+ } else if (_elapsingTo != null) {
+ throw StateError('Cannot elapse until previous elapse is complete.');
+ }
+
+ _elapsingTo = _elapsed + duration;
+ _fireTimersWhile((next) => next._nextCall <= _elapsingTo!);
+ _elapseTo(_elapsingTo!);
+ _elapsingTo = null;
+ }
+
+ /// Simulates the synchronous passage of time, resulting from blocking or
+ /// expensive calls.
+ ///
+ /// Neither timers nor microtasks are run during this call, but if this is
+ /// called within [elapse] they may fire afterwards.
+ ///
+ /// Throws an [ArgumentError] if [duration] is negative.
+ void elapseBlocking(Duration duration) {
+ if (duration.inMicroseconds < 0) {
+ throw ArgumentError('Cannot call elapse with negative duration');
+ }
+
+ _elapsed += duration;
+ final elapsingTo = _elapsingTo;
+ if (elapsingTo != null && _elapsed > elapsingTo) _elapsingTo = _elapsed;
+ }
+
+ /// Runs [callback] in a [Zone] where all asynchrony is controlled by `this`.
+ ///
+ /// All [Future]s, [Stream]s, [Timer]s, microtasks, and other time-based
+ /// asynchronous features used within [callback] are controlled by calls to
+ /// [elapse] rather than the passing of real time.
+ ///
+ /// The [`clock`][] property will be set to a clock that reports the fake
+ /// elapsed time. By default, it starts at the time the [FakeAsync] was
+ /// created (according to [`clock.now()`][]), but this can be controlled by
+ /// passing `initialTime` to [FakeAsync.new].
+ ///
+ /// [`clock`]: https://www.dartdocs.org/documentation/clock/latest/clock/clock.html
+ /// [`clock.now()`]: https://www.dartdocs.org/documentation/clock/latest/clock/Clock/now.html
+ ///
+ /// Calls [callback] with `this` as argument and returns its result.
+ ///
+ /// Note: it's usually more convenient to use [fakeAsync] rather than creating
+ /// a [FakeAsync] object and calling [run] manually.
+ T run<T>(T Function(FakeAsync self) callback) =>
+ runZoned(() => withClock(_clock, () => callback(this)),
+ zoneSpecification: ZoneSpecification(
+ createTimer: (_, __, ___, duration, callback) =>
+ _createTimer(duration, callback, false),
+ createPeriodicTimer: (_, __, ___, duration, callback) =>
+ _createTimer(duration, callback, true),
+ scheduleMicrotask: (_, __, ___, microtask) =>
+ _microtasks.add(microtask)));
+
+ /// Runs all pending microtasks scheduled within a call to [run] or
+ /// [fakeAsync] until there are no more microtasks scheduled.
+ ///
+ /// Does not run timers.
+ void flushMicrotasks() {
+ while (_microtasks.isNotEmpty) {
+ _microtasks.removeFirst()();
+ }
+ }
+
+ /// Elapses time until there are no more active timers.
+ ///
+ /// If `flushPeriodicTimers` is `true` (the default), this will repeatedly run
+ /// periodic timers until they're explicitly canceled. Otherwise, this will
+ /// stop when the only active timers are periodic.
+ ///
+ /// The [timeout] controls how much fake time may elapse before a [StateError]
+ /// is thrown. This ensures that a periodic timer doesn't cause this method to
+ /// deadlock. It defaults to one hour.
+ void flushTimers(
+ {Duration timeout = const Duration(hours: 1),
+ bool flushPeriodicTimers = true}) {
+ final absoluteTimeout = _elapsed + timeout;
+ _fireTimersWhile((timer) {
+ if (timer._nextCall > absoluteTimeout) {
+ // TODO(nweiz): Make this a [TimeoutException].
+ throw StateError('Exceeded timeout $timeout while flushing timers');
+ }
+
+ if (flushPeriodicTimers) return _timers.isNotEmpty;
+
+ // Continue firing timers until the only ones left are periodic *and*
+ // every periodic timer has had a change to run against the final
+ // value of [_elapsed].
+ return _timers
+ .any((timer) => !timer.isPeriodic || timer._nextCall <= _elapsed);
+ });
+ }
+
+ /// Invoke the callback for each timer until [predicate] returns `false` for
+ /// the next timer that would be fired.
+ ///
+ /// Microtasks are flushed before and after each timer is fired. Before each
+ /// timer fires, [_elapsed] is updated to the appropriate duration.
+ void _fireTimersWhile(bool Function(FakeTimer timer) predicate) {
+ flushMicrotasks();
+ for (;;) {
+ if (_timers.isEmpty) break;
+
+ final timer = minBy(_timers, (FakeTimer timer) => timer._nextCall)!;
+ if (!predicate(timer)) break;
+
+ _elapseTo(timer._nextCall);
+ timer._fire();
+ flushMicrotasks();
+ }
+ }
+
+ /// Creates a new timer controlled by `this` that fires [callback] after
+ /// [duration] (or every [duration] if [periodic] is `true`).
+ Timer _createTimer(Duration duration, Function callback, bool periodic) {
+ final timer = FakeTimer._(duration, callback, periodic, this,
+ includeStackTrace: includeTimerStackTrace);
+ _timers.add(timer);
+ return timer;
+ }
+
+ /// Sets [_elapsed] to [to] if [to] is longer than [_elapsed].
+ void _elapseTo(Duration to) {
+ if (to > _elapsed) _elapsed = to;
+ }
+}
+
+/// An implementation of [Timer] that's controlled by a [FakeAsync].
+class FakeTimer implements Timer {
+ /// If this is periodic, the time that should elapse between firings of this
+ /// timer.
+ ///
+ /// This is not used by non-periodic timers.
+ final Duration duration;
+
+ /// The callback to invoke when the timer fires.
+ ///
+ /// For periodic timers, this is a `void Function(Timer)`. For non-periodic
+ /// timers, it's a `void Function()`.
+ final Function _callback;
+
+ /// Whether this is a periodic timer.
+ final bool isPeriodic;
+
+ /// The [FakeAsync] instance that controls this timer.
+ final FakeAsync _async;
+
+ /// The value of [FakeAsync._elapsed] at (or after) which this timer should be
+ /// fired.
+ late Duration _nextCall;
+
+ /// The current stack trace when this timer was created.
+ ///
+ /// If [FakeAsync.includeTimerStackTrace] is set to false then accessing
+ /// this field will throw a [TypeError].
+ StackTrace get creationStackTrace => _creationStackTrace!;
+ final StackTrace? _creationStackTrace;
+
+ var _tick = 0;
+
+ @override
+ int get tick => _tick;
+
+ /// Returns debugging information to try to identify the source of the
+ /// [Timer].
+ String get debugString => 'Timer (duration: $duration, periodic: $isPeriodic)'
+ '${_creationStackTrace != null ? ', created:\n$creationStackTrace' : ''}';
+
+ FakeTimer._(Duration duration, this._callback, this.isPeriodic, this._async,
+ {bool includeStackTrace = true})
+ : duration = duration < Duration.zero ? Duration.zero : duration,
+ _creationStackTrace = includeStackTrace ? StackTrace.current : null {
+ _nextCall = _async._elapsed + this.duration;
+ }
+
+ @override
+ bool get isActive => _async._timers.contains(this);
+
+ @override
+ void cancel() => _async._timers.remove(this);
+
+ /// Fires this timer's callback and updates its state as necessary.
+ void _fire() {
+ assert(isActive);
+ _tick++;
+ if (isPeriodic) {
+ _nextCall += duration;
+ // ignore: avoid_dynamic_calls
+ _callback(this);
+ } else {
+ cancel();
+ // ignore: avoid_dynamic_calls
+ _callback();
+ }
+ }
+}
diff --git a/pkgs/fake_async/mono_pkg.yaml b/pkgs/fake_async/mono_pkg.yaml
new file mode 100644
index 0000000..b447edb
--- /dev/null
+++ b/pkgs/fake_async/mono_pkg.yaml
@@ -0,0 +1,15 @@
+# See https://pub.dev/packages/mono_repo
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+ sdk: dev
+ - group:
+ - analyze
+ sdk: pubspec
+- unit_test:
+ - group:
+ - command: dart test
+ sdk: [dev, pubspec]
diff --git a/pkgs/fake_async/pubspec.yaml b/pkgs/fake_async/pubspec.yaml
new file mode 100644
index 0000000..f345860
--- /dev/null
+++ b/pkgs/fake_async/pubspec.yaml
@@ -0,0 +1,17 @@
+name: fake_async
+version: 1.3.2
+description: >-
+ Fake asynchronous events such as timers and microtasks for deterministic
+ testing.
+repository: https://github.com/dart-lang/test/tree/master/pkgs/fake_async
+environment:
+ sdk: ^3.3.0
+
+dependencies:
+ clock: ^1.1.0
+ collection: ^1.15.0
+
+dev_dependencies:
+ async: ^2.5.0
+ dart_flutter_team_lints: ^2.0.0
+ test: ^1.16.0
diff --git a/pkgs/fake_async/test/fake_async_test.dart b/pkgs/fake_async/test/fake_async_test.dart
new file mode 100644
index 0000000..463eecd
--- /dev/null
+++ b/pkgs/fake_async/test/fake_async_test.dart
@@ -0,0 +1,663 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:clock/clock.dart';
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ final initialTime = DateTime(2000);
+ final elapseBy = const Duration(days: 1);
+
+ test('should set initial time', () {
+ expect(FakeAsync().getClock(initialTime).now(), initialTime);
+ });
+
+ group('elapseBlocking', () {
+ test('should elapse time without calling timers', () {
+ Timer(elapseBy ~/ 2, neverCalled);
+ FakeAsync().elapseBlocking(elapseBy);
+ });
+
+ test('should elapse time by the specified amount', () {
+ final async = FakeAsync()..elapseBlocking(elapseBy);
+ expect(async.elapsed, elapseBy);
+ });
+
+ test('should throw when called with a negative duration', () {
+ expect(() => FakeAsync().elapseBlocking(const Duration(days: -1)),
+ throwsArgumentError);
+ });
+ });
+
+ group('elapse', () {
+ test('should elapse time by the specified amount', () {
+ FakeAsync().run((async) {
+ async.elapse(elapseBy);
+ expect(async.elapsed, elapseBy);
+ });
+ });
+
+ test('should throw ArgumentError when called with a negative duration', () {
+ expect(() => FakeAsync().elapse(const Duration(days: -1)),
+ throwsArgumentError);
+ });
+
+ test('should throw when called before previous call is complete', () {
+ FakeAsync().run((async) {
+ Timer(elapseBy ~/ 2, expectAsync0(() {
+ expect(() => async.elapse(elapseBy), throwsStateError);
+ }));
+ async.elapse(elapseBy);
+ });
+ });
+
+ group('when creating timers', () {
+ test('should call timers expiring before or at end time', () {
+ FakeAsync().run((async) {
+ Timer(elapseBy ~/ 2, expectAsync0(() {}));
+ Timer(elapseBy, expectAsync0(() {}));
+ async.elapse(elapseBy);
+ });
+ });
+
+ test('should call timers expiring due to elapseBlocking', () {
+ FakeAsync().run((async) {
+ Timer(elapseBy, () => async.elapseBlocking(elapseBy));
+ Timer(elapseBy * 2, expectAsync0(() {}));
+ async.elapse(elapseBy);
+ expect(async.elapsed, elapseBy * 2);
+ });
+ });
+
+ test('should call timers at their scheduled time', () {
+ FakeAsync().run((async) {
+ Timer(elapseBy ~/ 2, expectAsync0(() {
+ expect(async.elapsed, elapseBy ~/ 2);
+ }));
+
+ final periodicCalledAt = <Duration>[];
+ Timer.periodic(
+ elapseBy ~/ 2, (_) => periodicCalledAt.add(async.elapsed));
+
+ async.elapse(elapseBy);
+ expect(periodicCalledAt, [elapseBy ~/ 2, elapseBy]);
+ });
+ });
+
+ test('should not call timers expiring after end time', () {
+ FakeAsync().run((async) {
+ Timer(elapseBy * 2, neverCalled);
+ async.elapse(elapseBy);
+ });
+ });
+
+ test('should not call canceled timers', () {
+ FakeAsync().run((async) {
+ Timer(elapseBy ~/ 2, neverCalled).cancel();
+ async.elapse(elapseBy);
+ });
+ });
+
+ test('should call periodic timers each time the duration elapses', () {
+ FakeAsync().run((async) {
+ Timer.periodic(elapseBy ~/ 10, expectAsync1((_) {}, count: 10));
+ async.elapse(elapseBy);
+ });
+ });
+
+ test('should call timers occurring at the same time in FIFO order', () {
+ FakeAsync().run((async) {
+ final log = <String>[];
+ Timer(elapseBy ~/ 2, () => log.add('1'));
+ Timer(elapseBy ~/ 2, () => log.add('2'));
+ async.elapse(elapseBy);
+ expect(log, ['1', '2']);
+ });
+ });
+
+ test('should maintain FIFO order even with periodic timers', () {
+ FakeAsync().run((async) {
+ final log = <String>[];
+ Timer.periodic(elapseBy ~/ 2, (_) => log.add('periodic 1'));
+ Timer(elapseBy ~/ 2, () => log.add('delayed 1'));
+ Timer(elapseBy, () => log.add('delayed 2'));
+ Timer.periodic(elapseBy, (_) => log.add('periodic 2'));
+
+ async.elapse(elapseBy);
+ expect(log, [
+ 'periodic 1',
+ 'delayed 1',
+ 'periodic 1',
+ 'delayed 2',
+ 'periodic 2'
+ ]);
+ });
+ });
+
+ test('should process microtasks surrounding each timer', () {
+ FakeAsync().run((async) {
+ var microtaskCalls = 0;
+ var timerCalls = 0;
+ void scheduleMicrotasks() {
+ for (var i = 0; i < 5; i++) {
+ scheduleMicrotask(() => microtaskCalls++);
+ }
+ }
+
+ scheduleMicrotasks();
+ Timer.periodic(elapseBy ~/ 5, (_) {
+ timerCalls++;
+ expect(microtaskCalls, 5 * timerCalls);
+ scheduleMicrotasks();
+ });
+ async.elapse(elapseBy);
+ expect(timerCalls, 5);
+ expect(microtaskCalls, 5 * (timerCalls + 1));
+ });
+ });
+
+ test('should pass the periodic timer itself to callbacks', () {
+ FakeAsync().run((async) {
+ late Timer constructed;
+ constructed = Timer.periodic(elapseBy, expectAsync1((passed) {
+ expect(passed, same(constructed));
+ }));
+ async.elapse(elapseBy);
+ });
+ });
+
+ test('should call microtasks before advancing time', () {
+ FakeAsync().run((async) {
+ scheduleMicrotask(expectAsync0(() {
+ expect(async.elapsed, Duration.zero);
+ }));
+ async.elapse(const Duration(minutes: 1));
+ });
+ });
+
+ test('should add event before advancing time', () {
+ FakeAsync().run((async) {
+ final controller = StreamController<void>();
+ expect(controller.stream.first.then((_) {
+ expect(async.elapsed, Duration.zero);
+ }), completes);
+ controller.add(null);
+ async.elapse(const Duration(minutes: 1));
+ });
+ });
+
+ test('should increase negative duration timers to zero duration', () {
+ FakeAsync().run((async) {
+ final negativeDuration = const Duration(days: -1);
+ Timer(negativeDuration, expectAsync0(() {
+ expect(async.elapsed, Duration.zero);
+ }));
+ async.elapse(const Duration(minutes: 1));
+ });
+ });
+
+ test('should not be additive with elapseBlocking', () {
+ FakeAsync().run((async) {
+ Timer(Duration.zero, () => async.elapseBlocking(elapseBy * 5));
+ async.elapse(elapseBy);
+ expect(async.elapsed, elapseBy * 5);
+ });
+ });
+
+ group('isActive', () {
+ test('should be false after timer is run', () {
+ FakeAsync().run((async) {
+ final timer = Timer(elapseBy ~/ 2, () {});
+ async.elapse(elapseBy);
+ expect(timer.isActive, isFalse);
+ });
+ });
+
+ test('should be true after periodic timer is run', () {
+ FakeAsync().run((async) {
+ final timer = Timer.periodic(elapseBy ~/ 2, (_) {});
+ async.elapse(elapseBy);
+ expect(timer.isActive, isTrue);
+ });
+ });
+
+ test('should be false after timer is canceled', () {
+ FakeAsync().run((async) {
+ final timer = Timer(elapseBy ~/ 2, () {})..cancel();
+ expect(timer.isActive, isFalse);
+ });
+ });
+ });
+
+ test('should work with new Future()', () {
+ FakeAsync().run((async) {
+ Future(expectAsync0(() {}));
+ async.elapse(Duration.zero);
+ });
+ });
+
+ test('should work with Future.delayed', () {
+ FakeAsync().run((async) {
+ Future.delayed(elapseBy, expectAsync0(() {}));
+ async.elapse(elapseBy);
+ });
+ });
+
+ test('should work with Future.timeout', () {
+ FakeAsync().run((async) {
+ final completer = Completer<void>();
+ expect(completer.future.timeout(elapseBy ~/ 2),
+ throwsA(const TypeMatcher<TimeoutException>()));
+ async.elapse(elapseBy);
+ completer.complete();
+ });
+ });
+
+ // TODO: Pausing and resuming the timeout Stream doesn't work since
+ // it uses `new Stopwatch()`.
+ //
+ // See https://code.google.com/p/dart/issues/detail?id=18149
+ test('should work with Stream.periodic', () {
+ FakeAsync().run((async) {
+ expect(Stream.periodic(const Duration(minutes: 1), (i) => i),
+ emitsInOrder([0, 1, 2]));
+ async.elapse(const Duration(minutes: 3));
+ });
+ });
+
+ test('should work with Stream.timeout', () {
+ FakeAsync().run((async) {
+ final controller = StreamController<int>();
+ final timed = controller.stream.timeout(const Duration(minutes: 2));
+
+ final events = <int>[];
+ final errors = <Object>[];
+ timed.listen(events.add, onError: errors.add);
+
+ controller.add(0);
+ async.elapse(const Duration(minutes: 1));
+ expect(events, [0]);
+
+ async.elapse(const Duration(minutes: 1));
+ expect(errors, hasLength(1));
+ expect(errors.first, const TypeMatcher<TimeoutException>());
+ });
+ });
+ });
+ });
+
+ group('flushMicrotasks', () {
+ test('should flush a microtask', () {
+ FakeAsync().run((async) {
+ Future.microtask(expectAsync0(() {}));
+ async.flushMicrotasks();
+ });
+ });
+
+ test('should flush microtasks scheduled by microtasks in order', () {
+ FakeAsync().run((async) {
+ final log = <int>[];
+ scheduleMicrotask(() {
+ log.add(1);
+ scheduleMicrotask(() => log.add(3));
+ });
+ scheduleMicrotask(() => log.add(2));
+
+ async.flushMicrotasks();
+ expect(log, [1, 2, 3]);
+ });
+ });
+
+ test('should not run timers', () {
+ FakeAsync().run((async) {
+ final log = <int>[];
+ scheduleMicrotask(() => log.add(1));
+ Timer.run(() => log.add(2));
+ Timer.periodic(const Duration(seconds: 1), (_) => log.add(2));
+
+ async.flushMicrotasks();
+ expect(log, [1]);
+ });
+ });
+ });
+
+ group('flushTimers', () {
+ test('should flush timers in FIFO order', () {
+ FakeAsync().run((async) {
+ final log = <int>[];
+ Timer.run(() {
+ log.add(1);
+ Timer(elapseBy, () => log.add(3));
+ });
+ Timer.run(() => log.add(2));
+
+ async.flushTimers(timeout: elapseBy * 2);
+ expect(log, [1, 2, 3]);
+ expect(async.elapsed, elapseBy);
+ });
+ });
+
+ test(
+ 'should run collateral periodic timers with non-periodic first if '
+ 'scheduled first', () {
+ FakeAsync().run((async) {
+ final log = <String>[];
+ Timer(const Duration(seconds: 2), () => log.add('delayed'));
+ Timer.periodic(const Duration(seconds: 1), (_) => log.add('periodic'));
+
+ async.flushTimers(flushPeriodicTimers: false);
+ expect(log, ['periodic', 'delayed', 'periodic']);
+ });
+ });
+
+ test(
+ 'should run collateral periodic timers with periodic first '
+ 'if scheduled first', () {
+ FakeAsync().run((async) {
+ final log = <String>[];
+ Timer.periodic(const Duration(seconds: 1), (_) => log.add('periodic'));
+ Timer(const Duration(seconds: 2), () => log.add('delayed'));
+
+ async.flushTimers(flushPeriodicTimers: false);
+ expect(log, ['periodic', 'periodic', 'delayed']);
+ });
+ });
+
+ test('should time out', () {
+ FakeAsync().run((async) {
+ // Schedule 3 timers. All but the last one should fire.
+ for (var delay in [30, 60, 90]) {
+ Timer(Duration(minutes: delay),
+ expectAsync0(() {}, count: delay == 90 ? 0 : 1));
+ }
+
+ expect(() => async.flushTimers(), throwsStateError);
+ });
+ });
+
+ test('should time out a chain of timers', () {
+ FakeAsync().run((async) {
+ var count = 0;
+ void createTimer() {
+ Timer(const Duration(minutes: 30), () {
+ count++;
+ createTimer();
+ });
+ }
+
+ createTimer();
+ expect(() => async.flushTimers(timeout: const Duration(hours: 2)),
+ throwsStateError);
+ expect(count, 4);
+ });
+ });
+
+ test('should time out periodic timers', () {
+ FakeAsync().run((async) {
+ Timer.periodic(
+ const Duration(minutes: 30), expectAsync1((_) {}, count: 2));
+ expect(() => async.flushTimers(timeout: const Duration(hours: 1)),
+ throwsStateError);
+ });
+ });
+
+ test('should flush periodic timers', () {
+ FakeAsync().run((async) {
+ var count = 0;
+ Timer.periodic(const Duration(minutes: 30), (timer) {
+ if (count == 3) timer.cancel();
+ count++;
+ });
+ async.flushTimers(timeout: const Duration(hours: 20));
+ expect(count, 4);
+ });
+ });
+
+ test('should compute absolute timeout as elapsed + timeout', () {
+ FakeAsync().run((async) {
+ var count = 0;
+ void createTimer() {
+ Timer(const Duration(minutes: 30), () {
+ count++;
+ if (count < 4) createTimer();
+ });
+ }
+
+ createTimer();
+ async
+ ..elapse(const Duration(hours: 1))
+ ..flushTimers(timeout: const Duration(hours: 1));
+ expect(count, 4);
+ });
+ });
+ });
+
+ group('stats', () {
+ test('should report the number of pending microtasks', () {
+ FakeAsync().run((async) {
+ expect(async.microtaskCount, 0);
+ scheduleMicrotask(() {});
+ expect(async.microtaskCount, 1);
+ scheduleMicrotask(() {});
+ expect(async.microtaskCount, 2);
+ async.flushMicrotasks();
+ expect(async.microtaskCount, 0);
+ });
+ });
+
+ test('it should report the number of pending periodic timers', () {
+ FakeAsync().run((async) {
+ expect(async.periodicTimerCount, 0);
+ final timer = Timer.periodic(const Duration(minutes: 30), (_) {});
+ expect(async.periodicTimerCount, 1);
+ Timer.periodic(const Duration(minutes: 20), (_) {});
+ expect(async.periodicTimerCount, 2);
+ async.elapse(const Duration(minutes: 20));
+ expect(async.periodicTimerCount, 2);
+ timer.cancel();
+ expect(async.periodicTimerCount, 1);
+ });
+ });
+
+ test('it should report the number of pending non periodic timers', () {
+ FakeAsync().run((async) {
+ expect(async.nonPeriodicTimerCount, 0);
+ final timer = Timer(const Duration(minutes: 30), () {});
+ expect(async.nonPeriodicTimerCount, 1);
+ Timer(const Duration(minutes: 20), () {});
+ expect(async.nonPeriodicTimerCount, 2);
+ async.elapse(const Duration(minutes: 25));
+ expect(async.nonPeriodicTimerCount, 1);
+ timer.cancel();
+ expect(async.nonPeriodicTimerCount, 0);
+ });
+ });
+
+ test('should report debugging information of pending timers', () {
+ FakeAsync().run((fakeAsync) {
+ expect(fakeAsync.pendingTimers, isEmpty);
+ final nonPeriodic =
+ Timer(const Duration(seconds: 1), () {}) as FakeTimer;
+ final periodic =
+ Timer.periodic(const Duration(seconds: 2), (Timer timer) {})
+ as FakeTimer;
+ final debugInfo = fakeAsync.pendingTimers;
+ expect(debugInfo.length, 2);
+ expect(
+ debugInfo,
+ containsAll([
+ nonPeriodic,
+ periodic,
+ ]),
+ );
+
+ const thisFileName = 'fake_async_test.dart';
+ expect(nonPeriodic.debugString, contains(':01.0'));
+ expect(nonPeriodic.debugString, contains('periodic: false'));
+ expect(nonPeriodic.debugString, contains(thisFileName));
+ expect(periodic.debugString, contains(':02.0'));
+ expect(periodic.debugString, contains('periodic: true'));
+ expect(periodic.debugString, contains(thisFileName));
+ });
+ });
+
+ test(
+ 'should report debugging information of pending timers excluding '
+ 'stack traces', () {
+ FakeAsync(includeTimerStackTrace: false).run((fakeAsync) {
+ expect(fakeAsync.pendingTimers, isEmpty);
+ final nonPeriodic =
+ Timer(const Duration(seconds: 1), () {}) as FakeTimer;
+ final periodic =
+ Timer.periodic(const Duration(seconds: 2), (Timer timer) {})
+ as FakeTimer;
+ final debugInfo = fakeAsync.pendingTimers;
+ expect(debugInfo.length, 2);
+ expect(
+ debugInfo,
+ containsAll([
+ nonPeriodic,
+ periodic,
+ ]),
+ );
+
+ const thisFileName = 'fake_async_test.dart';
+ expect(nonPeriodic.debugString, contains(':01.0'));
+ expect(nonPeriodic.debugString, contains('periodic: false'));
+ expect(nonPeriodic.debugString, isNot(contains(thisFileName)));
+ expect(periodic.debugString, contains(':02.0'));
+ expect(periodic.debugString, contains('periodic: true'));
+ expect(periodic.debugString, isNot(contains(thisFileName)));
+ });
+ });
+ });
+
+ group('timers', () {
+ test("should become inactive as soon as they're invoked", () {
+ return FakeAsync().run((async) {
+ late Timer timer;
+ timer = Timer(elapseBy, expectAsync0(() {
+ expect(timer.isActive, isFalse);
+ }));
+
+ expect(timer.isActive, isTrue);
+ async.elapse(elapseBy);
+ expect(timer.isActive, isFalse);
+ });
+ });
+
+ test('should increment tick in a non-periodic timer', () {
+ return FakeAsync().run((async) {
+ late Timer timer;
+ timer = Timer(elapseBy, expectAsync0(() {
+ expect(timer.tick, 1);
+ }));
+
+ expect(timer.tick, 0);
+ async.elapse(elapseBy);
+ });
+ });
+
+ test('should increment tick in a periodic timer', () {
+ return FakeAsync().run((async) {
+ final ticks = <int>[];
+ Timer.periodic(
+ elapseBy,
+ expectAsync1((timer) {
+ ticks.add(timer.tick);
+ }, count: 2));
+ async
+ ..elapse(elapseBy)
+ ..elapse(elapseBy);
+ expect(ticks, [1, 2]);
+ });
+ });
+
+ test('should update periodic timer state before invoking callback', () {
+ // Regression test for: https://github.com/dart-lang/fake_async/issues/88
+ FakeAsync().run((async) {
+ final log = <String>[];
+ Timer.periodic(const Duration(seconds: 2), (timer) {
+ log.add('periodic ${timer.tick}');
+ async.elapse(Duration.zero);
+ });
+ Timer(const Duration(seconds: 3), () {
+ log.add('single');
+ });
+
+ async.flushTimers(flushPeriodicTimers: false);
+ expect(log, ['periodic 1', 'single']);
+ });
+ });
+ });
+
+ group('clock', () {
+ test('updates following elapse()', () {
+ FakeAsync().run((async) {
+ final before = clock.now();
+ async.elapse(elapseBy);
+ expect(clock.now(), before.add(elapseBy));
+ });
+ });
+
+ test('updates following elapseBlocking()', () {
+ FakeAsync().run((async) {
+ final before = clock.now();
+ async.elapseBlocking(elapseBy);
+ expect(clock.now(), before.add(elapseBy));
+ });
+ });
+
+ group('starts at', () {
+ test('the time at which the FakeAsync was created', () {
+ final start = DateTime.now();
+ FakeAsync().run((async) {
+ expect(clock.now(), _closeToTime(start));
+ async.elapse(elapseBy);
+ expect(clock.now(), _closeToTime(start.add(elapseBy)));
+ });
+ });
+
+ test('the value of clock.now()', () {
+ final start = DateTime(1990, 8, 11);
+ withClock(Clock.fixed(start), () {
+ FakeAsync().run((async) {
+ expect(clock.now(), start);
+ async.elapse(elapseBy);
+ expect(clock.now(), start.add(elapseBy));
+ });
+ });
+ });
+
+ test('an explicit value', () {
+ final start = DateTime(1990, 8, 11);
+ FakeAsync(initialTime: start).run((async) {
+ expect(clock.now(), start);
+ async.elapse(elapseBy);
+ expect(clock.now(), start.add(elapseBy));
+ });
+ });
+ });
+ });
+}
+
+/// Returns a matcher that asserts that a [DateTime] is within 100ms of
+/// [expected].
+Matcher _closeToTime(DateTime expected) => predicate(
+ (actual) =>
+ expected.difference(actual as DateTime).inMilliseconds.abs() < 100,
+ 'is close to $expected');
diff --git a/pkgs/matcher/.gitignore b/pkgs/matcher/.gitignore
new file mode 100644
index 0000000..ab3cb76
--- /dev/null
+++ b/pkgs/matcher/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.dart_tool/
+.pub/
+build/
+packages
+.packages
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/pkgs/matcher/CHANGELOG.md b/pkgs/matcher/CHANGELOG.md
new file mode 100644
index 0000000..0522c4c
--- /dev/null
+++ b/pkgs/matcher/CHANGELOG.md
@@ -0,0 +1,289 @@
+## 0.12.18-wip
+
+* Remove some dynamic invocations.
+
+## 0.12.17
+
+* Require Dart 3.4
+* Move to `dart-lang/test` monorepo.
+
+## 0.12.16+1
+
+* Require Dart 3.0
+* Support latest version of `package:test_api`.
+
+## 0.12.16
+
+* Expand bounds on `test_api` dependency to allow the next breaking release
+ which will remove the cyclic dependency on this package.
+
+## 0.12.15
+
+* Add `package:matcher/expect.dart` library. Copies the implementation of
+ `expect` and the asynchronous matchers from `package:test`.
+
+## 0.12.14
+
+* Add `containsOnce` matcher.
+* Deprecate `isCyclicInitializationError` and `NullThrownError`. These errors
+ will be removed from the SDK. Update them to catch more general errors.
+
+## 0.12.13
+
+* Require Dart 2.17 or greater.
+* Make `isCastError` no longer depend on the deprecated `CastError` type.
+* Annotate `TypeMatcher.having` with `useResult`.
+
+## 0.12.12
+
+* Add a best practices section to readme.
+* Populate the pubspec `repository` field.
+
+## 0.12.11
+
+* Change many argument types from `dynamic` to `Object?`.
+* Fix `stringContainsInOrder` to account for repetitions and empty strings.
+ * **Note**: This may break some existing tests, as the behavior does change.
+
+## 0.12.10
+
+* Stable release for null safety.
+
+## 0.12.10-nullsafety.3
+
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 0.12.10-nullsafety.2
+
+- Allow prerelease versions of the 2.12 sdk.
+
+## 0.12.10-nullsafety.1
+
+- Allow 2.10 stable and 2.11.0 dev SDK versions.
+
+## 0.12.10-nullsafety
+
+- Migrate to NNBD.
+ - Apis have been updated to express intent of the existing code and how it
+ handled nulls.
+
+## 0.12.9
+
+- Improve mismatch descriptions for deep matches. Previously, if the user tried
+ to do a deep match where the expectation included a complex matcher (such as a
+ "having" matcher), the failure message would just say "failed to match ...";
+ it wouldn't call on the expectation's matcher to explain why the match failed.
+
+## 0.12.8
+
+- Add a mismatch description to `TypeMatcher`.
+
+## 0.12.7
+
+- Deprecate the `mirror_matchers.dart` library.
+
+## 0.12.6
+
+- Update minimum Dart SDK to `2.2.0`.
+- Consistently point to `isA` as a replacement for `instanceOf`.
+- Pretty print with private type names.
+
+## 0.12.5
+
+- Add `isA()` to create `TypeMatcher` instances in a more fluent way.
+- **Potentially breaking bug fix**. Ordering matchers no longer treat objects
+ with a partial ordering (such as NaN for double values) as if they had a
+ complete ordering. For instance `greaterThan` now compares with the `>`
+ operator rather not `<` and not `=`. This could cause tests which relied on
+ this bug to start failing.
+
+## 0.12.4
+
+- Add isCastError.
+
+## 0.12.3+1
+
+- Set max SDK version to <3.0.0, and adjusted other dependencies.
+
+## 0.12.3
+
+- Many improvements to `TypeMatcher`
+ - Can now be used directly as `const TypeMatcher<MyType>()`.
+ - Added a type parameter to specify the target `Type`.
+ - Made the `name` constructor parameter optional and marked it deprecated.
+ It's redundant to the type parameter.
+ - Migrated all `isType` matchers to `TypeMatcher`.
+ - Added a `having` function that allows chained validations of specific
+ features of the target type.
+
+ ```dart
+ /// Validates that the object is a [RangeError] with a message containing
+ /// the string 'details' and `start` and `end` properties that are `null`.
+ final _rangeMatcher = isRangeError
+ .having((e) => e.message, 'message', contains('details'))
+ .having((e) => e.start, 'start', isNull)
+ .having((e) => e.end, 'end', isNull);
+ ```
+
+- Deprecated the `isInstanceOf` class. Use `TypeMatcher` instead.
+
+- Improved the output of `Matcher` instances that fail due to type errors.
+
+## 0.12.2+1
+
+- Updated SDK version to 2.0.0-dev.17.0
+
+## 0.12.2
+
+* Fixed `unorderedMatches` in cases where the matchers may match more than one
+ element and order of the elements doesn't line up with the order of the
+ matchers.
+
+* Add containsAll matcher for Iterables. This Matcher checks that all
+ values/matchers in an expected iterable are satisfied by an element in the
+ value without allowing the same value to satisfy multiple matchers.
+
+## 0.12.1+4
+
+* Fixed SDK constraint to allow edge builds.
+
+## 0.12.1+3
+
+* Make `predicate` and `pairwiseCompare` generic methods to allow typed
+ functions to be passed to them as arguments.
+
+* Make internal implementations take better advantage of type promotion to avoid
+ dynamic call overhead.
+
+## 0.12.1+2
+
+* Fixed small documentation issues.
+
+* Fixed small issue in `StringEqualsMatcher`.
+
+* Update to support future Dart language changes.
+
+## 0.12.1+1
+
+* Produce a better error message when a `CustomMatcher`'s feature throws.
+
+## 0.12.1
+
+* Add containsAllInOrder matcher for Iterables
+
+## 0.12.0+2
+
+* Fix all strong-mode warnings.
+
+## 0.12.0+1
+
+* Fix test files to use `test` instead of `unittest` pkg.
+
+## 0.12.0
+
+* Moved a number of members to the
+ [`unittest`](https://pub.dev/packages/unittest) package.
+ * `TestFailure`, `ErrorFormatter`, `expect`, `fail`, and 'wrapAsync'.
+ * `completes`, `completion`, `throws`, and `throwsA` Matchers.
+ * The `Throws` class.
+ * All of the `throws...Error` Matchers.
+
+* Removed `FailureHandler`, `DefaultFailureHandler`,
+ `configureExpectFailureHandler`, and `getOrCreateExpectFailureHandler`.
+ Now that `expect` is in the `unittest` package, these are no longer needed.
+
+* Removed the `name` parameter for `isInstanceOf`. This was previously
+ deprecated, and is no longer necessary since all language implementations now
+ support converting the type parameter to a string directly.
+
+## 0.11.4+6
+
+* Fix a bug introduced in 0.11.4+5 in which operator matchers broke when taking
+ lists of matchers.
+
+## 0.11.4+5
+
+* Fix all strong-mode warnings.
+
+## 0.11.4+4
+
+* Deprecate the name parameter to `isInstanceOf`. All language implementations
+ now support converting the type parameter to a string directly.
+
+## 0.11.4+3
+
+* Fix the examples for `equalsIgnoringWhitespace`.
+
+## 0.11.4+2
+
+* Improve the formatting of strings that contain unprintable ASCII characters.
+
+## 0.11.4+1
+
+* Correctly match and print `String`s containing characters that must be
+ represented as escape sequences.
+
+## 0.11.4
+
+* Remove the type checks in the `isEmpty` and `isNotEmpty` matchers and simply
+ access the `isEmpty` respectively `isNotEmpty` fields. This allows them to
+ work with custom collections. See [Issue
+ 21792](https://code.google.com/p/dart/issues/detail?id=21792) and [Issue
+ 21562](https://code.google.com/p/dart/issues/detail?id=21562)
+
+## 0.11.3+1
+
+* Fix the `prints` matcher test on dart2js.
+
+## 0.11.3
+
+* Add a `prints` matcher that matches output a callback emits via `print`.
+
+## 0.11.2
+
+* Add an `isNotEmpty` matcher.
+
+## 0.11.1+1
+
+* Refactored libraries and tests.
+
+* Fixed spelling mistake.
+
+## 0.11.1
+
+* Added `isNaN` and `isNotNaN` matchers.
+
+## 0.11.0
+
+* Removed deprecated matchers.
+
+## 0.10.1+1
+
+* Get the tests passing when run on dart2js in minified mode.
+
+## 0.10.1
+
+* Compare sets order-independently when using `equals()`.
+
+## 0.10.0+3
+
+* Removed `@deprecated` annotation on matchers due to
+[Issue 19173](https://code.google.com/p/dart/issues/detail?id=19173)
+
+## 0.10.0+2
+
+* Added types to a number of constants.
+
+## 0.10.0+1
+
+* Matchers related to bad language use have been removed. These represent code
+structure that should rarely or never be validated in tests.
+ * `isAbstractClassInstantiationError`
+ * `throwsAbstractClassInstantiationError`
+ * `isFallThroughError`
+ * `throwsFallThroughError`
+
+* Added types to a number of method arguments.
+
+* The structure of the library and test code has been updated.
diff --git a/pkgs/matcher/LICENSE b/pkgs/matcher/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/matcher/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/matcher/README.md b/pkgs/matcher/README.md
new file mode 100644
index 0000000..efcae13
--- /dev/null
+++ b/pkgs/matcher/README.md
@@ -0,0 +1,265 @@
+[](https://pub.dev/packages/matcher)
+[](https://pub.dev/packages/matcher/publisher)
+
+Support for specifying test expectations, such as for unit tests.
+
+The matcher library provides a third-generation assertion mechanism, drawing
+inspiration from [Hamcrest](https://code.google.com/p/hamcrest/).
+
+For more information on testing, see
+[Unit Testing with Dart](https://github.com/dart-lang/test/blob/master/pkgs/test/README.md#writing-tests).
+
+## Using matcher
+
+Expectations start with a call to [`expect()`] or [`expectAsync()`].
+
+[`expect()`]: https://pub.dev/documentation/matcher/latest/expect/expect.html
+[`expectAsync()`]: https://pub.dev/documentation/matcher/latest/expect/expectAsync.html
+
+Any matchers package can be used with `expect()` to do
+complex validations:
+
+[`matcher`]: https://pub.dev/documentation/matcher/latest/matcher/matcher-library.html
+
+```dart
+import 'package:test/test.dart';
+
+void main() {
+ test('.split() splits the string on the delimiter', () {
+ expect('foo,bar,baz', allOf([
+ contains('foo'),
+ isNot(startsWith('bar')),
+ endsWith('baz')
+ ]));
+ });
+}
+```
+
+If a non-matcher value is passed, it will be wrapped with [`equals()`].
+
+[`equals()`]: https://pub.dev/documentation/matcher/latest/expect/equals.html
+
+## Exception matchers
+
+You can also test exceptions with the [`throwsA()`] function or a matcher such
+as [`throwsFormatException`]:
+
+[`throwsA()`]: https://pub.dev/documentation/matcher/latest/expect/throwsA.html
+[`throwsFormatException`]: https://pub.dev/documentation/matcher/latest/expect/throwsFormatException-constant.html
+
+```dart
+import 'package:test/test.dart';
+
+void main() {
+ test('.parse() fails on invalid input', () {
+ expect(() => int.parse('X'), throwsFormatException);
+ });
+}
+```
+
+### Future Matchers
+
+There are a number of useful functions and matchers for more advanced
+asynchrony. The [`completion()`] matcher can be used to test `Futures`; it
+ensures that the test doesn't finish until the `Future` completes, and runs a
+matcher against that `Future`'s value.
+
+[`completion()`]: https://pub.dev/documentation/matcher/latest/expect/completion.html
+
+```dart
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test('Future.value() returns the value', () {
+ expect(Future.value(10), completion(equals(10)));
+ });
+}
+```
+
+The [`throwsA()`] matcher and the various [`throwsExceptionType`] matchers work
+with both synchronous callbacks and asynchronous `Future`s. They ensure that a
+particular type of exception is thrown:
+
+[`throwsExceptionType`]: https://pub.dev/documentation/matcher/latest/expect/throwsException-constant.html
+
+```dart
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test('Future.error() throws the error', () {
+ expect(Future.error('oh no'), throwsA(equals('oh no')));
+ expect(Future.error(StateError('bad state')), throwsStateError);
+ });
+}
+```
+
+The [`expectAsync()`] function wraps another function and has two jobs. First,
+it asserts that the wrapped function is called a certain number of times, and
+will cause the test to fail if it's called too often; second, it keeps the test
+from finishing until the function is called the requisite number of times.
+
+```dart
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test('Stream.fromIterable() emits the values in the iterable', () {
+ var stream = Stream.fromIterable([1, 2, 3]);
+
+ stream.listen(expectAsync1((number) {
+ expect(number, inInclusiveRange(1, 3));
+ }, count: 3));
+ });
+}
+```
+
+[`expectAsync()`]: https://pub.dev/documentation/matcher/latest/expect/expectAsync.html
+
+### Stream Matchers
+
+The `test` package provides a suite of powerful matchers for dealing with
+[asynchronous streams][Stream]. They're expressive and composable, and make it
+easy to write complex expectations about the values emitted by a stream. For
+example:
+
+[Stream]: https://api.dart.dev/stable/dart-async/Stream-class.html
+
+```dart
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test('process emits status messages', () {
+ // Dummy data to mimic something that might be emitted by a process.
+ var stdoutLines = Stream.fromIterable([
+ 'Ready.',
+ 'Loading took 150ms.',
+ 'Succeeded!'
+ ]);
+
+ expect(stdoutLines, emitsInOrder([
+ // Values match individual events.
+ 'Ready.',
+
+ // Matchers also run against individual events.
+ startsWith('Loading took'),
+
+ // Stream matchers can be nested. This asserts that one of two events are
+ // emitted after the "Loading took" line.
+ emitsAnyOf(['Succeeded!', 'Failed!']),
+
+ // By default, more events are allowed after the matcher finishes
+ // matching. This asserts instead that the stream emits a done event and
+ // nothing else.
+ emitsDone
+ ]));
+ });
+}
+```
+
+A stream matcher can also match the [`async`] package's [`StreamQueue`] class,
+which allows events to be requested from a stream rather than pushed to the
+consumer. The matcher will consume the matched events, but leave the rest of the
+queue alone so that it can still be used by the test, unlike a normal `Stream`
+which can only have one subscriber. For example:
+
+[`async`]: https://pub.dev/packages/async
+[`StreamQueue`]: https://pub.dev/documentation/async/latest/async/StreamQueue-class.html
+
+```dart
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('process emits a WebSocket URL', () async {
+ // Wrap the Stream in a StreamQueue so that we can request events.
+ var stdout = StreamQueue(Stream.fromIterable([
+ 'WebSocket URL:',
+ 'ws://localhost:1234/',
+ 'Waiting for connection...'
+ ]));
+
+ // Ignore lines from the process until it's about to emit the URL.
+ await expectLater(stdout, emitsThrough('WebSocket URL:'));
+
+ // Parse the next line as a URL.
+ var url = Uri.parse(await stdout.next);
+ expect(url.host, equals('localhost'));
+
+ // You can match against the same StreamQueue multiple times.
+ await expectLater(stdout, emits('Waiting for connection...'));
+ });
+}
+```
+
+The following built-in stream matchers are available:
+
+* [`emits()`] matches a single data event.
+* [`emitsError()`] matches a single error event.
+* [`emitsDone`] matches a single done event.
+* [`mayEmit()`] consumes events if they match an inner matcher, without
+ requiring them to match.
+* [`mayEmitMultiple()`] works like `mayEmit()`, but it matches events against
+ the matcher as many times as possible.
+* [`emitsAnyOf()`] consumes events matching one (or more) of several possible
+ matchers.
+* [`emitsInOrder()`] consumes events matching multiple matchers in a row.
+* [`emitsInAnyOrder()`] works like `emitsInOrder()`, but it allows the
+ matchers to match in any order.
+* [`neverEmits()`] matches a stream that finishes *without* matching an inner
+ matcher.
+
+You can also define your own custom stream matchers with [`StreamMatcher()`].
+
+[`emits()`]: https://pub.dev/documentation/matcher/latest/expect/emits.html
+[`emitsError()`]: https://pub.dev/documentation/matcher/latest/expect/emitsError.html
+[`emitsDone`]: https://pub.dev/documentation/matcher/latest/expect/emitsDone.html
+[`mayEmit()`]: https://pub.dev/documentation/matcher/latest/expect/mayEmit.html
+[`mayEmitMultiple()`]: https://pub.dev/documentation/matcher/latest/expect/mayEmitMultiple.html
+[`emitsAnyOf()`]: https://pub.dev/documentation/matcher/latest/expect/emitsAnyOf.html
+[`emitsInOrder()`]: https://pub.dev/documentation/matcher/latest/expect/emitsInOrder.html
+[`emitsInAnyOrder()`]: https://pub.dev/documentation/matcher/latest/expect/emitsInAnyOrder.html
+[`neverEmits()`]: https://pub.dev/documentation/matcher/latest/expect/neverEmits.html
+[`StreamMatcher()`]: https://pub.dev/documentation/matcher/latest/expect/StreamMatcher-class.html
+
+## Best Practices
+
+### Prefer semantically meaningful matchers to comparing derived values
+
+Matchers which have knowledge of the semantics that are tested are able to emit
+more meaningful messages which don't require reading test source to understand
+why the test failed. For instance compare the failures between
+`expect(someList.length, 1)`, and `expect(someList, hasLength(1))`:
+
+```
+// expect(someList.length, 1);
+ Expected: <1>
+ Actual: <2>
+```
+
+```
+// expect(someList, hasLength(1));
+ Expected: an object with length of <1>
+ Actual: ['expected value', 'unexpected value']
+ Which: has length of <2>
+
+```
+
+### Prefer TypeMatcher to predicate if the match can fail in multiple ways
+
+The `predicate` utility is a convenient shortcut for testing an arbitrary
+(synchronous) property of a value, but it discards context and failures are
+opaque. Different failure modes cannot be distinguished in the output which is
+determined by a single "description" argument. Using `isA<SomeType>()` and the
+`TypeMatcher.having` API to extract and test derived properties in a structured
+way brings the context of that structure through to failure messages, so
+failures for different reasons will have distinguishable and actionable failure
+messages.
diff --git a/pkgs/matcher/analysis_options.yaml b/pkgs/matcher/analysis_options.yaml
new file mode 100644
index 0000000..d183f7b
--- /dev/null
+++ b/pkgs/matcher/analysis_options.yaml
@@ -0,0 +1,30 @@
+include: package:lints/recommended.yaml
+
+linter:
+ rules:
+ - always_declare_return_types
+ - avoid_dynamic_calls
+ - avoid_private_typedef_functions
+ - avoid_unused_constructor_parameters
+ - cancel_subscriptions
+ - comment_references
+ - directives_ordering
+ - lines_longer_than_80_chars
+ - literal_only_boolean_expressions
+ - missing_whitespace_between_adjacent_strings
+ - no_adjacent_strings_in_list
+ - no_runtimeType_toString
+ - omit_local_variable_types
+ - only_throw_errors
+ - prefer_const_constructors
+ - prefer_relative_imports
+ - prefer_single_quotes
+ - test_types_in_equals
+ - throw_in_finally
+ - type_annotate_public_apis
+ - unawaited_futures
+ - unnecessary_await_in_return
+ - unnecessary_lambdas
+ - unnecessary_parenthesis
+ - unnecessary_statements
+ - use_super_parameters
diff --git a/pkgs/matcher/lib/expect.dart b/pkgs/matcher/lib/expect.dart
new file mode 100644
index 0000000..c842d30
--- /dev/null
+++ b/pkgs/matcher/lib/expect.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2021, 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+export 'matcher.dart';
+
+export 'src/expect/expect.dart' show ErrorFormatter, expect, expectLater, fail;
+export 'src/expect/expect_async.dart'
+ show
+ Func0,
+ Func1,
+ Func2,
+ Func3,
+ Func4,
+ Func5,
+ Func6,
+ expectAsync,
+ expectAsync0,
+ expectAsync1,
+ expectAsync2,
+ expectAsync3,
+ expectAsync4,
+ expectAsync5,
+ expectAsync6,
+ expectAsyncUntil0,
+ expectAsyncUntil1,
+ expectAsyncUntil2,
+ expectAsyncUntil3,
+ expectAsyncUntil4,
+ expectAsyncUntil5,
+ expectAsyncUntil6;
+export 'src/expect/future_matchers.dart'
+ show completes, completion, doesNotComplete;
+export 'src/expect/never_called.dart' show neverCalled;
+export 'src/expect/prints_matcher.dart' show prints;
+export 'src/expect/stream_matcher.dart' show StreamMatcher;
+export 'src/expect/stream_matchers.dart'
+ show
+ emitsDone,
+ emits,
+ emitsError,
+ mayEmit,
+ emitsAnyOf,
+ emitsInOrder,
+ emitsInAnyOrder,
+ emitsThrough,
+ mayEmitMultiple,
+ neverEmits;
+export 'src/expect/throws_matcher.dart' show Throws, throws, throwsA;
+export 'src/expect/throws_matchers.dart'
+ show
+ throwsArgumentError,
+ throwsConcurrentModificationError,
+ throwsCyclicInitializationError,
+ throwsException,
+ throwsFormatException,
+ throwsNoSuchMethodError,
+ throwsNullThrownError,
+ throwsRangeError,
+ throwsStateError,
+ throwsUnimplementedError,
+ throwsUnsupportedError;
diff --git a/pkgs/matcher/lib/matcher.dart b/pkgs/matcher/lib/matcher.dart
new file mode 100644
index 0000000..236d6f4
--- /dev/null
+++ b/pkgs/matcher/lib/matcher.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2014, 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.
+
+/// Support for specifying test expectations, such as for unit tests.
+library;
+
+export 'src/core_matchers.dart';
+export 'src/custom_matcher.dart';
+export 'src/description.dart';
+export 'src/equals_matcher.dart';
+export 'src/error_matchers.dart';
+export 'src/interfaces.dart';
+export 'src/iterable_matchers.dart';
+export 'src/map_matchers.dart';
+export 'src/numeric_matchers.dart';
+export 'src/operator_matchers.dart';
+export 'src/order_matchers.dart';
+export 'src/string_matchers.dart';
+export 'src/type_matcher.dart';
+export 'src/util.dart';
diff --git a/pkgs/matcher/lib/mirror_matchers.dart b/pkgs/matcher/lib/mirror_matchers.dart
new file mode 100644
index 0000000..5b2f4b6
--- /dev/null
+++ b/pkgs/matcher/lib/mirror_matchers.dart
@@ -0,0 +1,85 @@
+// Copyright (c) 2014, 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.
+
+@Deprecated('Check properties on known types')
+library;
+
+/// The mirror matchers library provides some additional matchers that
+/// make use of `dart:mirrors`.
+import 'dart:mirrors';
+
+import 'matcher.dart';
+
+/// Returns a matcher that checks if a class instance has a property
+/// with name [name], and optionally, if that property in turn satisfies
+/// a [matcher].
+Matcher hasProperty(String name, [Object? matcher]) =>
+ _HasProperty(name, matcher == null ? null : wrapMatcher(matcher));
+
+class _HasProperty extends Matcher {
+ final String _name;
+ final Matcher? _matcher;
+
+ const _HasProperty(this._name, [this._matcher]);
+
+ @override
+ bool matches(Object? item, Map matchState) {
+ var mirror = reflect(item);
+ var classMirror = mirror.type;
+ var symbol = Symbol(_name);
+ var candidate = classMirror.declarations[symbol];
+ if (candidate == null) {
+ addStateInfo(matchState, {'reason': 'has no property named "$_name"'});
+ return false;
+ }
+ var isInstanceField = candidate is VariableMirror && !candidate.isStatic;
+ var isInstanceGetter =
+ candidate is MethodMirror && candidate.isGetter && !candidate.isStatic;
+ if (!(isInstanceField || isInstanceGetter)) {
+ addStateInfo(matchState, {
+ 'reason':
+ 'has a member named "$_name", but it is not an instance property'
+ });
+ return false;
+ }
+ var matcher = _matcher;
+ if (matcher == null) return true;
+ var result = mirror.getField(symbol);
+ var resultMatches = matcher.matches(result.reflectee, matchState);
+ if (!resultMatches) {
+ addStateInfo(matchState, {'value': result.reflectee});
+ }
+ return resultMatches;
+ }
+
+ @override
+ Description describe(Description description) {
+ description.add('has property "$_name"');
+ if (_matcher != null) {
+ description.add(' which matches ').addDescriptionOf(_matcher);
+ }
+ return description;
+ }
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ var reason = matchState['reason'];
+ if (reason != null) {
+ mismatchDescription.add(reason as String);
+ } else {
+ mismatchDescription
+ .add('has property "$_name" with value ')
+ .addDescriptionOf(matchState['value']);
+ var innerDescription = StringDescription();
+ matchState['state'] ??= {};
+ _matcher?.describeMismatch(matchState['value'], innerDescription,
+ matchState['state'] as Map, verbose);
+ if (innerDescription.length > 0) {
+ mismatchDescription.add(' which ').add(innerDescription.toString());
+ }
+ }
+ return mismatchDescription;
+ }
+}
diff --git a/pkgs/matcher/lib/src/core_matchers.dart b/pkgs/matcher/lib/src/core_matchers.dart
new file mode 100644
index 0000000..afb835b
--- /dev/null
+++ b/pkgs/matcher/lib/src/core_matchers.dart
@@ -0,0 +1,327 @@
+// Copyright (c) 2012, 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 'feature_matcher.dart';
+import 'interfaces.dart';
+import 'type_matcher.dart';
+import 'util.dart';
+
+/// Returns a matcher that matches the isEmpty property.
+const Matcher isEmpty = _Empty();
+
+class _Empty extends Matcher {
+ const _Empty();
+
+ @override
+ bool matches(Object? item, Map matchState) => (item as dynamic).isEmpty;
+
+ @override
+ Description describe(Description description) => description.add('empty');
+}
+
+/// Returns a matcher that matches the isNotEmpty property.
+const Matcher isNotEmpty = _NotEmpty();
+
+class _NotEmpty extends Matcher {
+ const _NotEmpty();
+
+ @override
+ bool matches(Object? item, Map matchState) => (item as dynamic).isNotEmpty;
+
+ @override
+ Description describe(Description description) => description.add('non-empty');
+}
+
+/// A matcher that matches any null value.
+const Matcher isNull = _IsNull();
+
+/// A matcher that matches any non-null value.
+const Matcher isNotNull = _IsNotNull();
+
+class _IsNull extends Matcher {
+ const _IsNull();
+ @override
+ bool matches(Object? item, Map matchState) => item == null;
+ @override
+ Description describe(Description description) => description.add('null');
+}
+
+class _IsNotNull extends Matcher {
+ const _IsNotNull();
+ @override
+ bool matches(Object? item, Map matchState) => item != null;
+ @override
+ Description describe(Description description) => description.add('not null');
+}
+
+/// A matcher that matches the Boolean value true.
+const Matcher isTrue = _IsTrue();
+
+/// A matcher that matches anything except the Boolean value true.
+const Matcher isFalse = _IsFalse();
+
+class _IsTrue extends Matcher {
+ const _IsTrue();
+ @override
+ bool matches(Object? item, Map matchState) => item == true;
+ @override
+ Description describe(Description description) => description.add('true');
+}
+
+class _IsFalse extends Matcher {
+ const _IsFalse();
+ @override
+ bool matches(Object? item, Map matchState) => item == false;
+ @override
+ Description describe(Description description) => description.add('false');
+}
+
+/// A matcher that matches the numeric value NaN.
+const Matcher isNaN = _IsNaN();
+
+/// A matcher that matches any non-NaN value.
+const Matcher isNotNaN = _IsNotNaN();
+
+class _IsNaN extends FeatureMatcher<num> {
+ const _IsNaN();
+ @override
+ bool typedMatches(num item, Map matchState) =>
+ double.nan.compareTo(item) == 0;
+ @override
+ Description describe(Description description) => description.add('NaN');
+}
+
+class _IsNotNaN extends FeatureMatcher<num> {
+ const _IsNotNaN();
+ @override
+ bool typedMatches(num item, Map matchState) =>
+ double.nan.compareTo(item) != 0;
+ @override
+ Description describe(Description description) => description.add('not NaN');
+}
+
+/// Returns a matches that matches if the value is the same instance
+/// as [expected], using [identical].
+Matcher same(Object? expected) => _IsSameAs(expected);
+
+class _IsSameAs extends Matcher {
+ final Object? _expected;
+ const _IsSameAs(this._expected);
+ @override
+ bool matches(Object? item, Map matchState) => identical(item, _expected);
+ // If all types were hashable we could show a hash here.
+ @override
+ Description describe(Description description) =>
+ description.add('same instance as ').addDescriptionOf(_expected);
+}
+
+/// A matcher that matches any value.
+const Matcher anything = _IsAnything();
+
+class _IsAnything extends Matcher {
+ const _IsAnything();
+ @override
+ bool matches(Object? item, Map matchState) => true;
+ @override
+ Description describe(Description description) => description.add('anything');
+}
+
+/// **DEPRECATED** Use [isA] instead.
+///
+/// A matcher that matches if an object is an instance of [T] (or a subtype).
+@Deprecated('Use `isA<MyType>()` instead.')
+// ignore: camel_case_types
+class isInstanceOf<T> extends TypeMatcher<T> {
+ const isInstanceOf();
+}
+
+/// A matcher that matches a function call against no exception.
+///
+/// The function will be called once. Any exceptions will be silently swallowed.
+/// The value passed to expect() should be a reference to the function.
+/// Note that the function cannot take arguments; to handle this
+/// a wrapper will have to be created.
+const Matcher returnsNormally = _ReturnsNormally();
+
+class _ReturnsNormally extends FeatureMatcher<Function> {
+ const _ReturnsNormally();
+
+ @override
+ bool typedMatches(Function f, Map matchState) {
+ try {
+ // ignore: unnecessary_cast
+ (f as Function)();
+ return true;
+ } catch (e, s) {
+ addStateInfo(matchState, {'exception': e, 'stack': s});
+ return false;
+ }
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('return normally');
+
+ @override
+ Description describeTypedMismatch(Function item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']);
+ if (verbose) {
+ mismatchDescription.add(' at ').add(matchState['stack'].toString());
+ }
+ return mismatchDescription;
+ }
+}
+
+/// A matcher for [Map].
+const isMap = TypeMatcher<Map>();
+
+/// A matcher for [List].
+const isList = TypeMatcher<List>();
+
+/// Returns a matcher that matches if an object has a length property
+/// that matches [matcher].
+Matcher hasLength(Object? matcher) => _HasLength(wrapMatcher(matcher));
+
+class _HasLength extends Matcher {
+ final Matcher _matcher;
+ const _HasLength(this._matcher);
+
+ @override
+ bool matches(Object? item, Map matchState) {
+ try {
+ final length = (item as dynamic).length;
+ return _matcher.matches(length, matchState);
+ } catch (e) {
+ return false;
+ }
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('an object with length of ').addDescriptionOf(_matcher);
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ try {
+ final length = (item as dynamic).length;
+ return mismatchDescription.add('has length of ').addDescriptionOf(length);
+ } catch (e) {
+ return mismatchDescription.add('has no length property');
+ }
+ }
+}
+
+/// Returns a matcher that matches if the match argument contains the expected
+/// value.
+///
+/// For [String]s this means substring matching;
+/// for [Map]s it means the map has the key, and for [Iterable]s
+/// it means the iterable has a matching element. In the case of iterables,
+/// [expected] can itself be a matcher.
+Matcher contains(Object? expected) => _Contains(expected);
+
+class _Contains extends Matcher {
+ final Object? _expected;
+
+ const _Contains(this._expected);
+
+ @override
+ bool matches(Object? item, Map matchState) {
+ var expected = _expected;
+ if (item is String) {
+ return expected is Pattern && item.contains(expected);
+ } else if (item is Iterable) {
+ if (expected is Matcher) {
+ return item.any((e) => expected.matches(e, matchState));
+ } else {
+ return item.contains(_expected);
+ }
+ } else if (item is Map) {
+ return item.containsKey(_expected);
+ }
+ return false;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('contains ').addDescriptionOf(_expected);
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ if (item is String || item is Iterable || item is Map) {
+ super.describeMismatch(item, mismatchDescription, matchState, verbose);
+ mismatchDescription.add('does not contain ').addDescriptionOf(_expected);
+ return mismatchDescription;
+ } else {
+ return mismatchDescription.add('is not a string, map or iterable');
+ }
+ }
+}
+
+/// Returns a matcher that matches if the match argument is in
+/// the expected value. This is the converse of [contains].
+Matcher isIn(Object? expected) {
+ if (expected is Iterable) {
+ return _In(expected, expected.contains);
+ } else if (expected is String) {
+ return _In<Pattern>(expected, expected.contains);
+ } else if (expected is Map) {
+ return _In(expected, expected.containsKey);
+ }
+
+ throw ArgumentError.value(
+ expected, 'expected', 'Only Iterable, Map, and String are supported.');
+}
+
+class _In<T> extends FeatureMatcher<T> {
+ final Object _source;
+ final bool Function(T) _containsFunction;
+
+ const _In(this._source, this._containsFunction);
+
+ @override
+ bool typedMatches(T item, Map matchState) => _containsFunction(item);
+
+ @override
+ Description describe(Description description) =>
+ description.add('is in ').addDescriptionOf(_source);
+}
+
+/// Returns a matcher that uses an arbitrary function that returns whether the
+/// value is considered a match.
+///
+/// For example:
+///
+/// expect(actual, predicate<num>((v) => (v % 2) == 0, 'is even'));
+///
+/// Use this method when a value is checked for one conceptual property
+/// described by [description].
+///
+/// If the value can be rejected for more than one reason prefer using [isA] and
+/// the [TypeMatcher.having] API to build up a matcher with output that can
+/// distinquish between them.
+///
+/// Using an explicit generict argument allows a passed function literal to have
+/// an inferred argument type of [T], and values of the wrong type will be
+/// rejected with an informative message.
+Matcher predicate<T>(bool Function(T) f,
+ [String description = 'satisfies function']) =>
+ _Predicate(f, description);
+
+class _Predicate<T> extends FeatureMatcher<T> {
+ final bool Function(T) _matcher;
+ final String _description;
+
+ _Predicate(this._matcher, this._description);
+
+ @override
+ bool typedMatches(T item, Map matchState) => _matcher(item);
+
+ @override
+ Description describe(Description description) =>
+ description.add(_description);
+}
diff --git a/pkgs/matcher/lib/src/custom_matcher.dart b/pkgs/matcher/lib/src/custom_matcher.dart
new file mode 100644
index 0000000..b0f2d6b
--- /dev/null
+++ b/pkgs/matcher/lib/src/custom_matcher.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2012, 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 'package:stack_trace/stack_trace.dart';
+
+import 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// A base class for [Matcher] instances that match based on some feature of the
+/// value under test.
+///
+/// Derived classes should call the base constructor with a feature name and
+/// description, and an instance matcher, and should implement the
+/// [featureValueOf] abstract method.
+///
+/// The feature description will typically describe the item and the feature,
+/// while the feature name will just name the feature. For example, we may
+/// have a Widget class where each Widget has a price; we could make a
+/// [CustomMatcher] that can make assertions about prices with:
+///
+/// ```dart
+/// class HasPrice extends CustomMatcher {
+/// HasPrice(matcher) : super("Widget with price that is", "price", matcher);
+/// featureValueOf(actual) => (actual as Widget).price;
+/// }
+/// ```
+///
+/// and then use this for example like:
+///
+/// ```dart
+/// expect(inventoryItem, HasPrice(greaterThan(0)));
+/// ```
+class CustomMatcher extends Matcher {
+ final String _featureDescription;
+ final String _featureName;
+ final Matcher _matcher;
+
+ CustomMatcher(
+ this._featureDescription, this._featureName, Object? valueOrMatcher)
+ : _matcher = wrapMatcher(valueOrMatcher);
+
+ /// Override this to extract the interesting feature.
+ Object? featureValueOf(dynamic actual) => actual;
+
+ @override
+ bool matches(Object? item, Map matchState) {
+ try {
+ var f = featureValueOf(item);
+ if (_matcher.matches(f, matchState)) return true;
+ addStateInfo(matchState, {'custom.feature': f});
+ } catch (exception, stack) {
+ addStateInfo(matchState, {
+ 'custom.exception': exception.toString(),
+ 'custom.stack': Chain.forTrace(stack)
+ .foldFrames(
+ (frame) =>
+ frame.package == 'test' ||
+ frame.package == 'stream_channel' ||
+ frame.package == 'matcher',
+ terse: true)
+ .toString()
+ });
+ }
+ return false;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ if (matchState['custom.exception'] != null) {
+ mismatchDescription
+ .add('threw ')
+ .addDescriptionOf(matchState['custom.exception'])
+ .add('\n')
+ .add(matchState['custom.stack'].toString());
+ return mismatchDescription;
+ }
+
+ mismatchDescription
+ .add('has ')
+ .add(_featureName)
+ .add(' with value ')
+ .addDescriptionOf(matchState['custom.feature']);
+ var innerDescription = StringDescription();
+
+ _matcher.describeMismatch(matchState['custom.feature'], innerDescription,
+ matchState['state'] as Map, verbose);
+
+ if (innerDescription.length > 0) {
+ mismatchDescription.add(' which ').add(innerDescription.toString());
+ }
+ return mismatchDescription;
+ }
+}
diff --git a/pkgs/matcher/lib/src/description.dart b/pkgs/matcher/lib/src/description.dart
new file mode 100644
index 0000000..090aada
--- /dev/null
+++ b/pkgs/matcher/lib/src/description.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2012, 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 'interfaces.dart';
+import 'pretty_print.dart';
+
+/// The default implementation of [Description]. This should rarely need
+/// substitution, although conceivably it is a place where other languages
+/// could be supported.
+class StringDescription implements Description {
+ final StringBuffer _out = StringBuffer();
+
+ /// Initialize the description with initial contents [init].
+ StringDescription([String init = '']) {
+ _out.write(init);
+ }
+
+ @override
+ int get length => _out.length;
+
+ /// Get the description as a string.
+ @override
+ String toString() => _out.toString();
+
+ /// Append [text] to the description.
+ @override
+ Description add(String text) {
+ _out.write(text);
+ return this;
+ }
+
+ /// Change the value of the description.
+ @override
+ Description replace(String text) {
+ _out.clear();
+ return add(text);
+ }
+
+ /// Appends a description of [value]. If it is an IMatcher use its
+ /// describe method; if it is a string use its literal value after
+ /// escaping any embedded control characters; otherwise use its
+ /// toString() value and wrap it in angular "quotes".
+ @override
+ Description addDescriptionOf(Object? value) {
+ if (value is Matcher) {
+ value.describe(this);
+ } else {
+ add(prettyPrint(value, maxLineLength: 80, maxItems: 25));
+ }
+ return this;
+ }
+
+ /// Append an [Iterable] [list] of objects to the description, using the
+ /// specified [separator] and framing the list with [start]
+ /// and [end].
+ @override
+ Description addAll(
+ String start, String separator, String end, Iterable list) {
+ var separate = false;
+ add(start);
+ for (var item in list) {
+ if (separate) {
+ add(separator);
+ }
+ addDescriptionOf(item);
+ separate = true;
+ }
+ add(end);
+ return this;
+ }
+}
diff --git a/pkgs/matcher/lib/src/equals_matcher.dart b/pkgs/matcher/lib/src/equals_matcher.dart
new file mode 100644
index 0000000..5c4f4c5
--- /dev/null
+++ b/pkgs/matcher/lib/src/equals_matcher.dart
@@ -0,0 +1,327 @@
+// Copyright (c) 2018, 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 'feature_matcher.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher that matches if the value is structurally equal to
+/// [expected].
+///
+/// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
+/// for equality using `==` on the expected value.
+///
+/// For [Iterable]s and [Map]s, this will recursively match the elements. To
+/// handle cyclic structures a recursion depth [limit] can be provided. The
+/// default limit is 100. [Set]s will be compared order-independently.
+Matcher equals(Object? expected, [int limit = 100]) => expected is String
+ ? _StringEqualsMatcher(expected)
+ : _DeepMatcher(expected, limit);
+
+typedef _RecursiveMatcher = _Mismatch? Function(Object?, Object?, String, int);
+
+/// A special equality matcher for strings.
+class _StringEqualsMatcher extends FeatureMatcher<String> {
+ final String _value;
+
+ _StringEqualsMatcher(this._value);
+
+ @override
+ bool typedMatches(String item, Map matchState) => _value == item;
+
+ @override
+ Description describe(Description description) =>
+ description.addDescriptionOf(_value);
+
+ @override
+ Description describeTypedMismatch(String item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ var buff = StringBuffer();
+ buff.write('is different.');
+ var escapedItem = escape(item);
+ var escapedValue = escape(_value);
+ var minLength = escapedItem.length < escapedValue.length
+ ? escapedItem.length
+ : escapedValue.length;
+ var start = 0;
+ for (; start < minLength; start++) {
+ if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
+ break;
+ }
+ }
+ if (start == minLength) {
+ if (escapedValue.length < escapedItem.length) {
+ buff.write(' Both strings start the same, but the actual value also'
+ ' has the following trailing characters: ');
+ _writeTrailing(buff, escapedItem, escapedValue.length);
+ } else {
+ buff.write(' Both strings start the same, but the actual value is'
+ ' missing the following trailing characters: ');
+ _writeTrailing(buff, escapedValue, escapedItem.length);
+ }
+ } else {
+ buff.write('\nExpected: ');
+ _writeLeading(buff, escapedValue, start);
+ _writeTrailing(buff, escapedValue, start);
+ buff.write('\n Actual: ');
+ _writeLeading(buff, escapedItem, start);
+ _writeTrailing(buff, escapedItem, start);
+ buff.write('\n ');
+ for (var i = start > 10 ? 14 : start; i > 0; i--) {
+ buff.write(' ');
+ }
+ buff.write('^\n Differ at offset $start');
+ }
+
+ return mismatchDescription.add(buff.toString());
+ }
+
+ static void _writeLeading(StringBuffer buff, String s, int start) {
+ if (start > 10) {
+ buff.write('... ');
+ buff.write(s.substring(start - 10, start));
+ } else {
+ buff.write(s.substring(0, start));
+ }
+ }
+
+ static void _writeTrailing(StringBuffer buff, String s, int start) {
+ if (start + 10 > s.length) {
+ buff.write(s.substring(start));
+ } else {
+ buff.write(s.substring(start, start + 10));
+ buff.write(' ...');
+ }
+ }
+}
+
+class _DeepMatcher extends Matcher {
+ final Object? _expected;
+ final int _limit;
+
+ _DeepMatcher(this._expected, [int limit = 1000]) : _limit = limit;
+
+ _Mismatch? _compareIterables(Iterable expected, Object? actual,
+ _RecursiveMatcher matcher, int depth, String location) {
+ if (actual is Iterable) {
+ var expectedIterator = expected.iterator;
+ var actualIterator = actual.iterator;
+ for (var index = 0;; index++) {
+ // Advance in lockstep.
+ var expectedNext = expectedIterator.moveNext();
+ var actualNext = actualIterator.moveNext();
+
+ // If we reached the end of both, we succeeded.
+ if (!expectedNext && !actualNext) return null;
+
+ // Fail if their lengths are different.
+ var newLocation = '$location[$index]';
+ if (!expectedNext) {
+ return _Mismatch.simple(newLocation, actual, 'longer than expected');
+ }
+ if (!actualNext) {
+ return _Mismatch.simple(newLocation, actual, 'shorter than expected');
+ }
+
+ // Match the elements.
+ var rp = matcher(expectedIterator.current, actualIterator.current,
+ newLocation, depth);
+ if (rp != null) return rp;
+ }
+ } else {
+ return _Mismatch.simple(location, actual, 'is not Iterable');
+ }
+ }
+
+ _Mismatch? _compareSets(Set expected, Object? actual,
+ _RecursiveMatcher matcher, int depth, String location) {
+ if (actual is Iterable) {
+ var other = actual.toSet();
+
+ for (var expectedElement in expected) {
+ if (other.every((actualElement) =>
+ matcher(expectedElement, actualElement, location, depth) != null)) {
+ return _Mismatch(
+ location,
+ actual,
+ (description, verbose) => description
+ .add('does not contain ')
+ .addDescriptionOf(expectedElement));
+ }
+ }
+
+ if (other.length > expected.length) {
+ return _Mismatch.simple(location, actual, 'larger than expected');
+ } else if (other.length < expected.length) {
+ return _Mismatch.simple(location, actual, 'smaller than expected');
+ } else {
+ return null;
+ }
+ } else {
+ return _Mismatch.simple(location, actual, 'is not Iterable');
+ }
+ }
+
+ _Mismatch? _recursiveMatch(
+ Object? expected, Object? actual, String location, int depth) {
+ // If the expected value is a matcher, try to match it.
+ if (expected is Matcher) {
+ var matchState = {};
+ if (expected.matches(actual, matchState)) return null;
+ return _Mismatch(location, actual, (description, verbose) {
+ var oldLength = description.length;
+ expected.describeMismatch(actual, description, matchState, verbose);
+ if (depth > 0 && description.length == oldLength) {
+ description.add('does not match ');
+ expected.describe(description);
+ }
+ });
+ } else {
+ // Otherwise, test for equality.
+ try {
+ if (expected == actual) return null;
+ } catch (e) {
+ // TODO(gram): Add a test for this case.
+ return _Mismatch(
+ location,
+ actual,
+ (description, verbose) =>
+ description.add('== threw ').addDescriptionOf(e));
+ }
+ }
+
+ if (depth > _limit) {
+ return _Mismatch.simple(
+ location, actual, 'recursion depth limit exceeded');
+ }
+
+ // If _limit is 1 we can only recurse one level into object.
+ if (depth == 0 || _limit > 1) {
+ if (expected is Set) {
+ return _compareSets(
+ expected, actual, _recursiveMatch, depth + 1, location);
+ } else if (expected is Iterable) {
+ return _compareIterables(
+ expected, actual, _recursiveMatch, depth + 1, location);
+ } else if (expected is Map) {
+ if (actual is! Map) {
+ return _Mismatch.simple(location, actual, 'expected a map');
+ }
+ var err = (expected.length == actual.length)
+ ? ''
+ : 'has different length and ';
+ for (var key in expected.keys) {
+ if (!actual.containsKey(key)) {
+ return _Mismatch(
+ location,
+ actual,
+ (description, verbose) => description
+ .add('${err}is missing map key ')
+ .addDescriptionOf(key));
+ }
+ }
+
+ for (var key in actual.keys) {
+ if (!expected.containsKey(key)) {
+ return _Mismatch(
+ location,
+ actual,
+ (description, verbose) => description
+ .add('${err}has extra map key ')
+ .addDescriptionOf(key));
+ }
+ }
+
+ for (var key in expected.keys) {
+ var rp = _recursiveMatch(
+ expected[key], actual[key], "$location['$key']", depth + 1);
+ if (rp != null) return rp;
+ }
+
+ return null;
+ }
+ }
+
+ // If we have recursed, show the expected value too; if not, expect() will
+ // show it for us.
+ if (depth > 0) {
+ return _Mismatch(location, actual,
+ (description, verbose) => description.addDescriptionOf(expected),
+ instead: true);
+ } else {
+ return _Mismatch(location, actual, null);
+ }
+ }
+
+ @override
+ bool matches(Object? actual, Map matchState) {
+ var mismatch = _recursiveMatch(_expected, actual, '', 0);
+ if (mismatch == null) return true;
+ addStateInfo(matchState, {'mismatch': mismatch});
+ return false;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.addDescriptionOf(_expected);
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ var mismatch = matchState['mismatch'] as _Mismatch;
+ var describeProblem = mismatch.describeProblem;
+ if (mismatch.location.isNotEmpty) {
+ mismatchDescription
+ .add('at location ')
+ .add(mismatch.location)
+ .add(' is ')
+ .addDescriptionOf(mismatch.actual);
+ if (describeProblem != null) {
+ mismatchDescription
+ .add(' ${mismatch.instead ? 'instead of' : 'which'} ');
+ describeProblem(mismatchDescription, verbose);
+ }
+ } else {
+ // If we didn't get a good reason, that would normally be a
+ // simple 'is <value>' message. We only add that if the mismatch
+ // description is non empty (so we are supplementing the mismatch
+ // description).
+ if (describeProblem == null) {
+ if (mismatchDescription.length > 0) {
+ mismatchDescription.add('is ').addDescriptionOf(item);
+ }
+ } else {
+ describeProblem(mismatchDescription, verbose);
+ }
+ }
+ return mismatchDescription;
+ }
+}
+
+class _Mismatch {
+ /// A human-readable description of the location within the collection where
+ /// the mismatch occurred.
+ final String location;
+
+ /// The actual value found at [location].
+ final Object? actual;
+
+ /// Callback that can create a detailed description of the problem.
+ final void Function(Description, bool verbose)? describeProblem;
+
+ /// If `true`, [describeProblem] describes the expected value, so when the
+ /// final mismatch description is pieced together, it will be preceded by
+ /// `instead of` (e.g. `at location [2] is <3> instead of <4>`). If `false`,
+ /// [describeProblem] is a problem description from a sub-matcher, so when the
+ /// final mismatch description is pieced together, it will be preceded by
+ /// `which` (e.g. `at location [2] is <foo> which has length of 3`).
+ final bool instead;
+
+ _Mismatch(this.location, this.actual, this.describeProblem,
+ {this.instead = false});
+
+ _Mismatch.simple(this.location, this.actual, String problem)
+ : describeProblem = ((description, verbose) => description.add(problem)),
+ instead = false;
+}
diff --git a/pkgs/matcher/lib/src/error_matchers.dart b/pkgs/matcher/lib/src/error_matchers.dart
new file mode 100644
index 0000000..5b6238d
--- /dev/null
+++ b/pkgs/matcher/lib/src/error_matchers.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2012, 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 'type_matcher.dart';
+
+/// A matcher for [ArgumentError].
+const isArgumentError = TypeMatcher<ArgumentError>();
+
+/// A matcher for [TypeError].
+@Deprecated('CastError has been deprecated in favor of TypeError. ')
+const isCastError = TypeMatcher<TypeError>();
+
+/// A matcher for [ConcurrentModificationError].
+const isConcurrentModificationError =
+ TypeMatcher<ConcurrentModificationError>();
+
+/// A matcher for [Error].
+@Deprecated(
+ 'CyclicInitializationError is deprecated and will be removed in Dart 3. '
+ 'Use `isA<Error>()` instead.')
+const isCyclicInitializationError = TypeMatcher<Error>();
+
+/// A matcher for [Exception].
+const isException = TypeMatcher<Exception>();
+
+/// A matcher for [FormatException].
+const isFormatException = TypeMatcher<FormatException>();
+
+/// A matcher for [NoSuchMethodError].
+const isNoSuchMethodError = TypeMatcher<NoSuchMethodError>();
+
+/// A matcher for [TypeError].
+@Deprecated('NullThrownError is deprecated and will be removed in Dart 3. '
+ 'Use `isA<TypeError>()` instead.')
+const isNullThrownError = TypeMatcher<TypeError>();
+
+/// A matcher for [RangeError].
+const isRangeError = TypeMatcher<RangeError>();
+
+/// A matcher for [StateError].
+const isStateError = TypeMatcher<StateError>();
+
+/// A matcher for [UnimplementedError].
+const isUnimplementedError = TypeMatcher<UnimplementedError>();
+
+/// A matcher for [UnsupportedError].
+const isUnsupportedError = TypeMatcher<UnsupportedError>();
diff --git a/pkgs/matcher/lib/src/expect/async_matcher.dart b/pkgs/matcher/lib/src/expect/async_matcher.dart
new file mode 100644
index 0000000..854151d
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/async_matcher.dart
@@ -0,0 +1,68 @@
+// 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+import 'package:test_api/hooks.dart';
+
+import '../description.dart';
+import '../equals_matcher.dart';
+import '../interfaces.dart';
+import '../operator_matchers.dart';
+import '../type_matcher.dart';
+import 'expect.dart';
+
+/// A matcher that does asynchronous computation.
+///
+/// Rather than implementing [matches], subclasses implement [matchAsync].
+/// [AsyncMatcher.matches] ensures that the test doesn't complete until the
+/// returned future completes, and [expect] returns a future that completes when
+/// the returned future completes so that tests can wait for it.
+abstract class AsyncMatcher extends Matcher {
+ const AsyncMatcher();
+
+ /// Returns `null` if this matches [item], or a [String] description of the
+ /// failure if it doesn't match.
+ ///
+ /// This can return a [Future] or a synchronous value. If it returns a
+ /// [Future], neither [expect] nor the test will complete until that [Future]
+ /// completes.
+ ///
+ /// If this returns a [String] synchronously, [expect] will synchronously
+ /// throw a [TestFailure] and [matches] will synchronously return `false`.
+ dynamic /*FutureOr<String>*/ matchAsync(dynamic item);
+
+ @override
+ bool matches(dynamic item, Map matchState) {
+ final result = matchAsync(item);
+ expect(
+ result,
+ anyOf([
+ equals(null),
+ const TypeMatcher<Future>(),
+ const TypeMatcher<String>()
+ ]),
+ reason: 'matchAsync() may only return a String, a Future, or null.');
+
+ if (result is Future) {
+ final outstandingWork = TestHandle.current.markPending();
+ result.then((realResult) {
+ if (realResult != null) {
+ fail(formatFailure(this, item, realResult as String));
+ }
+ outstandingWork.complete();
+ });
+ } else if (result is String) {
+ matchState[this] = result;
+ return false;
+ }
+
+ return true;
+ }
+
+ @override
+ Description describeMismatch(dynamic item, Description mismatchDescription,
+ Map matchState, bool verbose) =>
+ StringDescription(matchState[this] as String);
+}
diff --git a/pkgs/matcher/lib/src/expect/expect.dart b/pkgs/matcher/lib/src/expect/expect.dart
new file mode 100644
index 0000000..8dd8cae
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/expect.dart
@@ -0,0 +1,161 @@
+// Copyright (c) 2015, 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+import 'package:test_api/hooks.dart';
+
+import '../description.dart';
+import '../equals_matcher.dart';
+import '../interfaces.dart';
+import '../operator_matchers.dart';
+import '../type_matcher.dart';
+import '../util.dart';
+import 'async_matcher.dart';
+import 'future_matchers.dart';
+import 'prints_matcher.dart';
+import 'throws_matcher.dart';
+import 'util/pretty_print.dart';
+
+/// The type used for functions that can be used to build up error reports
+/// upon failures in [expect].
+@Deprecated('Will be removed in 0.13.0.')
+typedef ErrorFormatter = String Function(Object? actual, Matcher matcher,
+ String? reason, Map matchState, bool verbose);
+
+/// Assert that [actual] matches [matcher].
+///
+/// This is the main assertion function. [reason] is optional and is typically
+/// not supplied, as a reason is generated from [matcher]; if [reason]
+/// is included it is appended to the reason generated by the matcher.
+///
+/// [matcher] can be a value in which case it will be wrapped in an
+/// [equals] matcher.
+///
+/// If the assertion fails a [TestFailure] is thrown.
+///
+/// If [skip] is a String or `true`, the assertion is skipped. The arguments are
+/// still evaluated, but [actual] is not verified to match [matcher]. If
+/// [actual] is a [Future], the test won't complete until the future emits a
+/// value.
+///
+/// If [skip] is a string, it should explain why the assertion is skipped; this
+/// reason will be printed when running the test.
+///
+/// Certain matchers, like [completion] and [throwsA], either match or fail
+/// asynchronously. When you use [expect] with these matchers, it ensures that
+/// the test doesn't complete until the matcher has either matched or failed. If
+/// you want to wait for the matcher to complete before continuing the test, you
+/// can call [expectLater] instead and `await` the result.
+void expect(dynamic actual, dynamic matcher,
+ {String? reason,
+ Object? /* String|bool */ skip,
+ @Deprecated('Will be removed in 0.13.0.') bool verbose = false,
+ @Deprecated('Will be removed in 0.13.0.') ErrorFormatter? formatter}) {
+ _expect(actual, matcher,
+ reason: reason, skip: skip, verbose: verbose, formatter: formatter);
+}
+
+/// Just like [expect], but returns a [Future] that completes when the matcher
+/// has finished matching.
+///
+/// For the [completes] and [completion] matchers, as well as [throwsA] and
+/// related matchers when they're matched against a [Future], the returned
+/// future completes when the matched future completes. For the [prints]
+/// matcher, it completes when the future returned by the callback completes.
+/// Otherwise, it completes immediately.
+///
+/// If the matcher fails asynchronously, that failure is piped to the returned
+/// future where it can be handled by user code.
+Future expectLater(dynamic actual, dynamic matcher,
+ {String? reason, Object? /* String|bool */ skip}) =>
+ _expect(actual, matcher, reason: reason, skip: skip);
+
+/// The implementation of [expect] and [expectLater].
+Future _expect(Object? actual, Object? matcher,
+ {String? reason, skip, bool verbose = false, ErrorFormatter? formatter}) {
+ final test = TestHandle.current;
+ formatter ??= (actual, matcher, reason, matchState, verbose) {
+ var mismatchDescription = StringDescription();
+ matcher.describeMismatch(actual, mismatchDescription, matchState, verbose);
+
+ return formatFailure(matcher, actual, mismatchDescription.toString(),
+ reason: reason);
+ };
+
+ if (skip != null && skip is! bool && skip is! String) {
+ throw ArgumentError.value(skip, 'skip', 'must be a bool or a String');
+ }
+
+ matcher = wrapMatcher(matcher);
+ if (skip != null && skip != false) {
+ String message;
+ if (skip is String) {
+ message = 'Skip expect: $skip';
+ } else if (reason != null) {
+ message = 'Skip expect ($reason).';
+ } else {
+ var description = StringDescription().addDescriptionOf(matcher);
+ message = 'Skip expect ($description).';
+ }
+
+ test.markSkipped(message);
+ return Future.sync(() {});
+ }
+
+ if (matcher is AsyncMatcher) {
+ // Avoid async/await so that expect() throws synchronously when possible.
+ var result = matcher.matchAsync(actual);
+ expect(
+ result,
+ anyOf([
+ equals(null),
+ const TypeMatcher<Future>(),
+ const TypeMatcher<String>()
+ ]),
+ reason: 'matchAsync() may only return a String, a Future, or null.');
+
+ if (result is String) {
+ fail(formatFailure(matcher, actual, result, reason: reason));
+ } else if (result is Future) {
+ final outstandingWork = test.markPending();
+ return result.then((realResult) {
+ if (realResult == null) return;
+ fail(formatFailure(matcher as Matcher, actual, realResult as String,
+ reason: reason));
+ }).whenComplete(
+ // Always remove this, in case the failure is caught and handled
+ // gracefully.
+ outstandingWork.complete);
+ }
+
+ return Future.sync(() {});
+ }
+
+ var matchState = {};
+ try {
+ if ((matcher as Matcher).matches(actual, matchState)) {
+ return Future.sync(() {});
+ }
+ } catch (e, trace) {
+ reason ??= '$e at $trace';
+ }
+ fail(formatter(actual, matcher as Matcher, reason, matchState, verbose));
+}
+
+/// Convenience method for throwing a new [TestFailure] with the provided
+/// [message].
+Never fail(String message) => throw TestFailure(message);
+
+// The default error formatter.
+@Deprecated('Will be removed in 0.13.0.')
+String formatFailure(Matcher expected, Object? actual, String which,
+ {String? reason}) {
+ var buffer = StringBuffer();
+ buffer.writeln(indent(prettyPrint(expected), first: 'Expected: '));
+ buffer.writeln(indent(prettyPrint(actual), first: ' Actual: '));
+ if (which.isNotEmpty) buffer.writeln(indent(which, first: ' Which: '));
+ if (reason != null) buffer.writeln(reason);
+ return buffer.toString();
+}
diff --git a/pkgs/matcher/lib/src/expect/expect_async.dart b/pkgs/matcher/lib/src/expect/expect_async.dart
new file mode 100644
index 0000000..88cf6f2
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/expect_async.dart
@@ -0,0 +1,586 @@
+// Copyright (c) 2015, 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 'package:test_api/hooks.dart';
+
+import 'util/placeholder.dart';
+
+// Function types returned by expectAsync# methods.
+
+typedef Func0<T> = T Function();
+typedef Func1<T, A> = T Function([A a]);
+typedef Func2<T, A, B> = T Function([A a, B b]);
+typedef Func3<T, A, B, C> = T Function([A a, B b, C c]);
+typedef Func4<T, A, B, C, D> = T Function([A a, B b, C c, D d]);
+typedef Func5<T, A, B, C, D, E> = T Function([A a, B b, C c, D d, E e]);
+typedef Func6<T, A, B, C, D, E, F> = T Function([A a, B b, C c, D d, E e, F f]);
+
+/// A wrapper for a function that ensures that it's called the appropriate
+/// number of times.
+///
+/// The containing test won't be considered to have completed successfully until
+/// this function has been called the appropriate number of times.
+///
+/// The wrapper function is accessible via [func]. It supports up to six
+/// optional and/or required positional arguments, but no named arguments.
+class _ExpectedFunction<T> {
+ /// The wrapped callback.
+ final Function _callback;
+
+ /// The minimum number of calls that are expected to be made to the function.
+ ///
+ /// If fewer calls than this are made, the test will fail.
+ final int _minExpectedCalls;
+
+ /// The maximum number of calls that are expected to be made to the function.
+ ///
+ /// If more calls than this are made, the test will fail.
+ final int _maxExpectedCalls;
+
+ /// A callback that should return whether the function is not expected to have
+ /// any more calls.
+ ///
+ /// This will be called after every time the function is run. The test case
+ /// won't be allowed to terminate until it returns `true`.
+ ///
+ /// This may be `null`. If so, the function is considered to be done after
+ /// it's been run once.
+ final bool Function()? _isDone;
+
+ /// A descriptive name for the function.
+ final String _id;
+
+ /// An optional description of why the function is expected to be called.
+ ///
+ /// If not passed, this will be an empty string.
+ final String _reason;
+
+ /// The number of times the function has been called.
+ int _actualCalls = 0;
+
+ /// The test in which this function was wrapped.
+ late final TestHandle _test;
+
+ /// Whether this function has been called the requisite number of times.
+ late bool _complete;
+
+ OutstandingWork? _outstandingWork;
+
+ /// Wraps [callback] in a function that asserts that it's called at least
+ /// [minExpected] times and no more than [maxExpected] times.
+ ///
+ /// If passed, [id] is used as a descriptive name fo the function and [reason]
+ /// as a reason it's expected to be called. If [isDone] is passed, the test
+ /// won't be allowed to complete until it returns `true`.
+ _ExpectedFunction(Function callback, int minExpected, int maxExpected,
+ {String? id, String? reason, bool Function()? isDone})
+ : _callback = callback,
+ _minExpectedCalls = minExpected,
+ _maxExpectedCalls =
+ (maxExpected == 0 && minExpected > 0) ? minExpected : maxExpected,
+ _isDone = isDone,
+ _reason = reason == null ? '' : '\n$reason',
+ _id = _makeCallbackId(id, callback) {
+ try {
+ _test = TestHandle.current;
+ } on OutsideTestException {
+ throw StateError('`expectAsync` must be called within a test.');
+ }
+
+ if (maxExpected > 0 && minExpected > maxExpected) {
+ throw ArgumentError('max ($maxExpected) may not be less than count '
+ '($minExpected).');
+ }
+
+ if (isDone != null || minExpected > 0) {
+ _outstandingWork = _test.markPending();
+ _complete = false;
+ } else {
+ _complete = true;
+ }
+ }
+
+ /// Tries to find a reasonable name for [callback].
+ ///
+ /// If [id] is passed, uses that. Otherwise, tries to determine a name from
+ /// calling `toString`. If no name can be found, returns the empty string.
+ static String _makeCallbackId(String? id, Function callback) {
+ if (id != null) return '$id ';
+
+ // If the callback is not an anonymous closure, try to get the
+ // name.
+ var toString = callback.toString();
+ var prefix = "Function '";
+ var start = toString.indexOf(prefix);
+ if (start == -1) return '';
+
+ start += prefix.length;
+ var end = toString.indexOf("'", start);
+ if (end == -1) return '';
+ return '${toString.substring(start, end)} ';
+ }
+
+ /// Returns a function that has the same number of positional arguments as the
+ /// wrapped function (up to a total of 6).
+ Function get func {
+ if (_callback is Function(Never, Never, Never, Never, Never, Never)) {
+ return max6;
+ }
+ if (_callback is Function(Never, Never, Never, Never, Never)) return max5;
+ if (_callback is Function(Never, Never, Never, Never)) return max4;
+ if (_callback is Function(Never, Never, Never)) return max3;
+ if (_callback is Function(Never, Never)) return max2;
+ if (_callback is Function(Never)) return max1;
+ if (_callback is Function()) return max0;
+
+ _outstandingWork?.complete();
+ throw ArgumentError(
+ 'The wrapped function has more than 6 required arguments');
+ }
+
+ // This indirection is critical. It ensures the returned function has an
+ // argument count of zero.
+ T max0() => max6();
+
+ T max1([Object? a0 = placeholder]) => max6(a0);
+
+ T max2([Object? a0 = placeholder, Object? a1 = placeholder]) => max6(a0, a1);
+
+ T max3(
+ [Object? a0 = placeholder,
+ Object? a1 = placeholder,
+ Object? a2 = placeholder]) =>
+ max6(a0, a1, a2);
+
+ T max4(
+ [Object? a0 = placeholder,
+ Object? a1 = placeholder,
+ Object? a2 = placeholder,
+ Object? a3 = placeholder]) =>
+ max6(a0, a1, a2, a3);
+
+ T max5(
+ [Object? a0 = placeholder,
+ Object? a1 = placeholder,
+ Object? a2 = placeholder,
+ Object? a3 = placeholder,
+ Object? a4 = placeholder]) =>
+ max6(a0, a1, a2, a3, a4);
+
+ T max6(
+ [Object? a0 = placeholder,
+ Object? a1 = placeholder,
+ Object? a2 = placeholder,
+ Object? a3 = placeholder,
+ Object? a4 = placeholder,
+ Object? a5 = placeholder]) =>
+ _run([a0, a1, a2, a3, a4, a5].where((a) => a != placeholder));
+
+ /// Runs the wrapped function with [args] and returns its return value.
+ T _run(Iterable args) {
+ // Note that in the old test, this returned `null` if it encountered an
+ // error, where now it just re-throws that error because Zone machinery will
+ // pass it to the invoker anyway.
+ try {
+ _actualCalls++;
+ if (_test.shouldBeDone) {
+ throw TestFailure(
+ 'Callback ${_id}called ($_actualCalls) after test case '
+ '${_test.name} had already completed.$_reason');
+ } else if (_maxExpectedCalls >= 0 && _actualCalls > _maxExpectedCalls) {
+ throw TestFailure('Callback ${_id}called more times than expected '
+ '($_maxExpectedCalls).$_reason');
+ }
+
+ return Function.apply(_callback, args.toList()) as T;
+ } finally {
+ _afterRun();
+ }
+ }
+
+ /// After each time the function is run, check to see if it's complete.
+ void _afterRun() {
+ if (_complete) return;
+ if (_minExpectedCalls > 0 && _actualCalls < _minExpectedCalls) return;
+ if (_isDone != null && !_isDone()) return;
+
+ // Mark this callback as complete and remove it from the test case's
+ // outstanding callback count; if that hits zero the test is done.
+ _complete = true;
+ _outstandingWork?.complete();
+ }
+}
+
+/// This function is deprecated because it doesn't work well with strong mode.
+/// Use [expectAsync0], [expectAsync1],
+/// [expectAsync2], [expectAsync3], [expectAsync4], [expectAsync5], or
+/// [expectAsync6] instead.
+@Deprecated('Will be removed in 0.13.0')
+Function expectAsync(Function callback,
+ {int count = 1, int max = 0, String? id, String? reason}) =>
+ _ExpectedFunction(callback, count, max, id: id, reason: reason).func;
+
+/// Informs the framework that the given [callback] of arity 0 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with zero arguments. See also
+/// [expectAsync1], [expectAsync2], [expectAsync3], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func0<T> expectAsync0<T>(T Function() callback,
+ {int count = 1, int max = 0, String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, count, max, id: id, reason: reason).max0;
+
+/// Informs the framework that the given [callback] of arity 1 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with one argument. See also
+/// [expectAsync0], [expectAsync2], [expectAsync3], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func1<T, A> expectAsync1<T, A>(T Function(A) callback,
+ {int count = 1, int max = 0, String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, count, max, id: id, reason: reason).max1;
+
+/// Informs the framework that the given [callback] of arity 2 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with two arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync3], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func2<T, A, B> expectAsync2<T, A, B>(T Function(A, B) callback,
+ {int count = 1, int max = 0, String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, count, max, id: id, reason: reason).max2;
+
+/// Informs the framework that the given [callback] of arity 3 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with three arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func3<T, A, B, C> expectAsync3<T, A, B, C>(T Function(A, B, C) callback,
+ {int count = 1, int max = 0, String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, count, max, id: id, reason: reason).max3;
+
+/// Informs the framework that the given [callback] of arity 4 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with four arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func4<T, A, B, C, D> expectAsync4<T, A, B, C, D>(
+ T Function(A, B, C, D) callback,
+ {int count = 1,
+ int max = 0,
+ String? id,
+ String? reason}) =>
+ _ExpectedFunction<T>(callback, count, max, id: id, reason: reason).max4;
+
+/// Informs the framework that the given [callback] of arity 5 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with five arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
+/// [expectAsync4], and [expectAsync6] for callbacks with different arity.
+Func5<T, A, B, C, D, E> expectAsync5<T, A, B, C, D, E>(
+ T Function(A, B, C, D, E) callback,
+ {int count = 1,
+ int max = 0,
+ String? id,
+ String? reason}) =>
+ _ExpectedFunction<T>(callback, count, max, id: id, reason: reason).max5;
+
+/// Informs the framework that the given [callback] of arity 6 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with six arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
+/// [expectAsync4], and [expectAsync5] for callbacks with different arity.
+Func6<T, A, B, C, D, E, F> expectAsync6<T, A, B, C, D, E, F>(
+ T Function(A, B, C, D, E, F) callback,
+ {int count = 1,
+ int max = 0,
+ String? id,
+ String? reason}) =>
+ _ExpectedFunction<T>(callback, count, max, id: id, reason: reason).max6;
+
+/// This function is deprecated because it doesn't work well with strong mode.
+/// Use [expectAsyncUntil0], [expectAsyncUntil1],
+/// [expectAsyncUntil2], [expectAsyncUntil3], [expectAsyncUntil4],
+/// [expectAsyncUntil5], or [expectAsyncUntil6] instead.
+@Deprecated('Will be removed in 0.13.0')
+Function expectAsyncUntil(Function callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction(callback, 0, -1, id: id, reason: reason, isDone: isDone)
+ .func;
+
+/// Informs the framework that the given [callback] of arity 0 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with zero arguments. See also
+/// [expectAsyncUntil1], [expectAsyncUntil2], [expectAsyncUntil3],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func0<T> expectAsyncUntil0<T>(T Function() callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, 0, -1,
+ id: id, reason: reason, isDone: isDone)
+ .max0;
+
+/// Informs the framework that the given [callback] of arity 1 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with one argument. See also
+/// [expectAsyncUntil0], [expectAsyncUntil2], [expectAsyncUntil3],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func1<T, A> expectAsyncUntil1<T, A>(
+ T Function(A) callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, 0, -1,
+ id: id, reason: reason, isDone: isDone)
+ .max1;
+
+/// Informs the framework that the given [callback] of arity 2 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with two arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil3],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func2<T, A, B> expectAsyncUntil2<T, A, B>(
+ T Function(A, B) callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, 0, -1,
+ id: id, reason: reason, isDone: isDone)
+ .max2;
+
+/// Informs the framework that the given [callback] of arity 3 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with three arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func3<T, A, B, C> expectAsyncUntil3<T, A, B, C>(
+ T Function(A, B, C) callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, 0, -1,
+ id: id, reason: reason, isDone: isDone)
+ .max3;
+
+/// Informs the framework that the given [callback] of arity 4 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with four arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil3], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func4<T, A, B, C, D> expectAsyncUntil4<T, A, B, C, D>(
+ T Function(A, B, C, D) callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, 0, -1,
+ id: id, reason: reason, isDone: isDone)
+ .max4;
+
+/// Informs the framework that the given [callback] of arity 5 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with five arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil3], [expectAsyncUntil4], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func5<T, A, B, C, D, E> expectAsyncUntil5<T, A, B, C, D, E>(
+ T Function(A, B, C, D, E) callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, 0, -1,
+ id: id, reason: reason, isDone: isDone)
+ .max5;
+
+/// Informs the framework that the given [callback] of arity 6 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with six arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil3], [expectAsyncUntil4], and [expectAsyncUntil5] for
+/// callbacks with different arity.
+Func6<T, A, B, C, D, E, F> expectAsyncUntil6<T, A, B, C, D, E, F>(
+ T Function(A, B, C, D, E, F) callback, bool Function() isDone,
+ {String? id, String? reason}) =>
+ _ExpectedFunction<T>(callback, 0, -1,
+ id: id, reason: reason, isDone: isDone)
+ .max6;
diff --git a/pkgs/matcher/lib/src/expect/future_matchers.dart b/pkgs/matcher/lib/src/expect/future_matchers.dart
new file mode 100644
index 0000000..407b9b8
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/future_matchers.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2012, 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+import 'package:test_api/hooks.dart' show pumpEventQueue;
+
+import '../description.dart';
+import '../interfaces.dart';
+import '../util.dart';
+import 'async_matcher.dart';
+import 'expect.dart';
+import 'throws_matcher.dart';
+import 'util/pretty_print.dart';
+
+/// Matches a [Future] that completes successfully with any value.
+///
+/// This creates an asynchronous expectation. The call to [expect] will return
+/// immediately and execution will continue. To wait for the future to
+/// complete and the expectation to run use [expectLater] and wait on the
+/// returned future.
+///
+/// To test that a Future completes with an exception, you can use [throws] and
+/// [throwsA].
+final Matcher completes = const _Completes(null);
+
+/// Matches a [Future] that completes successfully with a value that matches
+/// [matcher].
+///
+/// This creates an asynchronous expectation. The call to [expect] will return
+/// immediately and execution will continue. Later, when the future completes,
+/// the expectation against [matcher] will run. To wait for the future to
+/// complete and the expectation to run use [expectLater] and wait on the
+/// returned future.
+///
+/// To test that a Future completes with an exception, you can use [throws] and
+/// [throwsA].
+Matcher completion(Object? matcher,
+ [@Deprecated('this parameter is ignored') String? description]) =>
+ _Completes(wrapMatcher(matcher));
+
+class _Completes extends AsyncMatcher {
+ final Matcher? _matcher;
+
+ const _Completes(this._matcher);
+
+ // Avoid async/await so we synchronously start listening to [item].
+ @override
+ dynamic /*FutureOr<String>*/ matchAsync(Object? item) {
+ if (item is! Future) return 'was not a Future';
+
+ return item.then((value) async {
+ if (_matcher == null) return null;
+
+ String? result;
+ if (_matcher is AsyncMatcher) {
+ result = await _matcher.matchAsync(value) as String?;
+ if (result == null) return null;
+ } else {
+ var matchState = {};
+ if (_matcher.matches(value, matchState)) return null;
+ result = _matcher
+ .describeMismatch(value, StringDescription(), matchState, false)
+ .toString();
+ }
+
+ var buffer = StringBuffer();
+ buffer.writeln(indent(prettyPrint(value), first: 'emitted '));
+ if (result.isNotEmpty) buffer.writeln(indent(result, first: ' which '));
+ return buffer.toString().trimRight();
+ });
+ }
+
+ @override
+ Description describe(Description description) {
+ if (_matcher == null) {
+ description.add('completes successfully');
+ } else {
+ description.add('completes to a value that ').addDescriptionOf(_matcher);
+ }
+ return description;
+ }
+}
+
+/// Matches a [Future] that does not complete.
+///
+/// Note that this creates an asynchronous expectation. The call to
+/// `expect()` that includes this will return immediately and execution will
+/// continue.
+final Matcher doesNotComplete = const _DoesNotComplete();
+
+class _DoesNotComplete extends Matcher {
+ const _DoesNotComplete();
+
+ @override
+ Description describe(Description description) {
+ description.add('does not complete');
+ return description;
+ }
+
+ @override
+ bool matches(Object? item, Map matchState) {
+ if (item is! Future) return false;
+ item.then((value) {
+ fail('Future was not expected to complete but completed with a value of '
+ '$value');
+ });
+ expect(pumpEventQueue(), completes);
+ return true;
+ }
+
+ @override
+ Description describeMismatch(
+ Object? item, Description description, Map matchState, bool verbose) {
+ if (item is! Future) return description.add('$item is not a Future');
+ return description;
+ }
+}
diff --git a/pkgs/matcher/lib/src/expect/never_called.dart b/pkgs/matcher/lib/src/expect/never_called.dart
new file mode 100644
index 0000000..20b5299
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/never_called.dart
@@ -0,0 +1,68 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test_api/hooks.dart';
+
+import 'expect.dart';
+import 'future_matchers.dart';
+import 'util/placeholder.dart';
+import 'util/pretty_print.dart';
+
+/// Returns a function that causes the test to fail if it's called.
+///
+/// This can safely be passed in place of any callback that takes ten or fewer
+/// positional parameters. For example:
+///
+/// ```dart
+/// // Asserts that the stream never emits an event.
+/// stream.listen(neverCalled);
+/// ```
+///
+/// This also ensures that the test doesn't complete until a call to
+/// [pumpEventQueue] finishes, so that the callback has a chance to be called.
+Null Function(
+ [Object?,
+ Object?,
+ Object?,
+ Object?,
+ Object?,
+ Object?,
+ Object?,
+ Object?,
+ Object?,
+ Object?]) get neverCalled {
+ // Make sure the test stays alive long enough to call the function if it's
+ // going to.
+ expect(pumpEventQueue(), completes);
+
+ var zone = Zone.current;
+ return (
+ [a1 = placeholder,
+ a2 = placeholder,
+ a3 = placeholder,
+ a4 = placeholder,
+ a5 = placeholder,
+ a6 = placeholder,
+ a7 = placeholder,
+ a8 = placeholder,
+ a9 = placeholder,
+ a10 = placeholder]) {
+ var arguments = [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]
+ .where((argument) => argument != placeholder)
+ .toList();
+
+ var argsText = arguments.isEmpty
+ ? ' no arguments.'
+ : ':\n${bullet(arguments.map(prettyPrint))}';
+ zone.handleUncaughtError(
+ TestFailure(
+ 'Callback should never have been called, but it was called with'
+ '$argsText'),
+ zone.run(Chain.current));
+ return null;
+ };
+}
diff --git a/pkgs/matcher/lib/src/expect/prints_matcher.dart b/pkgs/matcher/lib/src/expect/prints_matcher.dart
new file mode 100644
index 0000000..57ae95e
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/prints_matcher.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2014, 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 '../description.dart';
+import '../interfaces.dart';
+import '../util.dart';
+import 'async_matcher.dart';
+import 'expect.dart';
+import 'util/pretty_print.dart';
+
+/// Matches a [Function] that prints text that matches [matcher].
+///
+/// [matcher] may be a String or a [Matcher].
+///
+/// If the function this runs against returns a [Future], all text printed by
+/// the function (using [Zone] scoping) until that Future completes is matched.
+///
+/// This only tracks text printed using the [print] function.
+///
+/// This returns an [AsyncMatcher], so [expect] won't complete until the matched
+/// function does.
+Matcher prints(Object? matcher) => _Prints(wrapMatcher(matcher));
+
+class _Prints extends AsyncMatcher {
+ final Matcher _matcher;
+
+ _Prints(this._matcher);
+
+ // Avoid async/await so we synchronously fail if the function is
+ // synchronous.
+ @override
+ dynamic /*FutureOr<String>*/ matchAsync(Object? item) {
+ if (item is! Function()) return 'was not a unary Function';
+
+ var buffer = StringBuffer();
+ var result = runZoned(item,
+ zoneSpecification: ZoneSpecification(print: (_, __, ____, line) {
+ buffer.writeln(line);
+ }));
+
+ return result is Future
+ ? result.then((_) => _check(buffer.toString()))
+ : _check(buffer.toString());
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('prints ').addDescriptionOf(_matcher);
+
+ /// Verifies that [actual] matches [_matcher] and returns a [String]
+ /// description of the failure if it doesn't.
+ String? _check(String actual) {
+ var matchState = {};
+ if (_matcher.matches(actual, matchState)) return null;
+
+ var result = _matcher
+ .describeMismatch(actual, StringDescription(), matchState, false)
+ .toString();
+
+ var buffer = StringBuffer();
+ if (actual.isEmpty) {
+ buffer.writeln('printed nothing');
+ } else {
+ buffer.writeln(indent(prettyPrint(actual), first: 'printed '));
+ }
+ if (result.isNotEmpty) buffer.writeln(indent(result, first: ' which '));
+ return buffer.toString().trimRight();
+ }
+}
diff --git a/pkgs/matcher/lib/src/expect/stream_matcher.dart b/pkgs/matcher/lib/src/expect/stream_matcher.dart
new file mode 100644
index 0000000..0c1d852
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/stream_matcher.dart
@@ -0,0 +1,196 @@
+// 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.
+
+import 'package:async/async.dart';
+import 'package:test_api/hooks.dart';
+
+import '../interfaces.dart';
+import 'async_matcher.dart';
+import 'expect.dart';
+import 'util/pretty_print.dart';
+
+/// A matcher that matches events from [Stream]s or [StreamQueue]s.
+///
+/// Stream matchers are designed to make it straightforward to create complex
+/// expectations for streams, and to interleave expectations with the rest of a
+/// test. They can be used on a [Stream] to match all events it emits:
+///
+/// ```dart
+/// expect(stream, emitsInOrder([
+/// // Values match individual events.
+/// "Ready.",
+///
+/// // Matchers also run against individual events.
+/// startsWith("Loading took"),
+///
+/// // Stream matchers can be nested. This asserts that one of two events are
+/// // emitted after the "Loading took" line.
+/// emitsAnyOf(["Succeeded!", "Failed!"]),
+///
+/// // By default, more events are allowed after the matcher finishes
+/// // matching. This asserts instead that the stream emits a done event and
+/// // nothing else.
+/// emitsDone
+/// ]));
+/// ```
+///
+/// It can also match a [StreamQueue], in which case it consumes the matched
+/// events. The call to [expect] returns a [Future] that completes when the
+/// matcher is done matching. You can `await` this to consume different events
+/// at different times:
+///
+/// ```dart
+/// var stdout = StreamQueue(stdoutLineStream);
+///
+/// // Ignore lines from the process until it's about to emit the URL.
+/// await expectLater(stdout, emitsThrough('WebSocket URL:'));
+///
+/// // Parse the next line as a URL.
+/// var url = Uri.parse(await stdout.next);
+/// expect(url.host, equals('localhost'));
+///
+/// // You can match against the same StreamQueue multiple times.
+/// await expectLater(stdout, emits('Waiting for connection...'));
+/// ```
+///
+/// Users can call [StreamMatcher] to create custom matchers.
+abstract class StreamMatcher extends Matcher {
+ /// The description of this matcher.
+ ///
+ /// This is in the subjunctive mood, which means it can be used after the word
+ /// "should". For example, it might be "emit the right events".
+ String get description;
+
+ /// Creates a new [StreamMatcher] described by [description] that matches
+ /// events with [matchQueue].
+ ///
+ /// The [matchQueue] callback is used to implement [StreamMatcher.matchQueue],
+ /// and should follow all the guarantees of that method. In particular:
+ ///
+ /// * If it matches successfully, it should return `null` and possibly consume
+ /// events.
+ /// * If it fails to match, consume no events and return a description of the
+ /// failure.
+ /// * The description should be in past tense.
+ /// * The description should be grammatically valid when used after "the
+ /// stream"—"emitted the wrong events", for example.
+ ///
+ /// The [matchQueue] callback may return the empty string to indicate a
+ /// failure if it has no information to add beyond the description of the
+ /// failure and the events actually emitted by the stream.
+ ///
+ /// The [description] should be in the subjunctive mood. This means that it
+ /// should be grammatically valid when used after the word "should". For
+ /// example, it might be "emit the right events".
+ factory StreamMatcher(Future<String?> Function(StreamQueue) matchQueue,
+ String description) = _StreamMatcher;
+
+ /// Tries to match events emitted by [queue].
+ ///
+ /// If this matches successfully, it consumes the matching events from [queue]
+ /// and returns `null`.
+ ///
+ /// If this fails to match, it doesn't consume any events and returns a
+ /// description of the failure. This description is in the past tense, and
+ /// could grammatically be used after "the stream". For example, it might
+ /// return "emitted the wrong events".
+ ///
+ /// The description string may also be empty, which indicates that the
+ /// matcher's description and the events actually emitted by the stream are
+ /// enough to understand the failure.
+ ///
+ /// If the queue emits an error, that error is re-thrown unless otherwise
+ /// indicated by the matcher.
+ Future<String?> matchQueue(StreamQueue queue);
+}
+
+/// A concrete implementation of [StreamMatcher].
+///
+/// This is separate from the original type to hide the private [AsyncMatcher]
+/// interface.
+class _StreamMatcher extends AsyncMatcher implements StreamMatcher {
+ @override
+ final String description;
+
+ /// The callback used to implement [matchQueue].
+ final Future<String?> Function(StreamQueue) _matchQueue;
+
+ _StreamMatcher(this._matchQueue, this.description);
+
+ @override
+ Future<String?> matchQueue(StreamQueue queue) => _matchQueue(queue);
+
+ @override
+ dynamic /*FutureOr<String>*/ matchAsync(Object? item) {
+ StreamQueue queue;
+ var shouldCancelQueue = false;
+ if (item is StreamQueue) {
+ queue = item;
+ } else if (item is Stream) {
+ queue = StreamQueue(item);
+ shouldCancelQueue = true;
+ } else {
+ return 'was not a Stream or a StreamQueue';
+ }
+
+ // Avoid async/await in the outer method so that we synchronously error out
+ // for an invalid argument type.
+ var transaction = queue.startTransaction();
+ var copy = transaction.newQueue();
+ return matchQueue(copy).then((result) async {
+ // Accept the transaction if the result is null, indicating that the match
+ // succeeded.
+ if (result == null) {
+ transaction.commit(copy);
+ return null;
+ }
+
+ // Get a list of events emitted by the stream so we can emit them as part
+ // of the error message.
+ var replay = transaction.newQueue();
+ var events = <Result?>[];
+ var subscription = Result.captureStreamTransformer
+ .bind(replay.rest.cast())
+ .listen(events.add, onDone: () => events.add(null));
+
+ // Wait on a timer tick so all buffered events are emitted.
+ await Future.delayed(Duration.zero);
+ _unawaited(subscription.cancel());
+
+ var eventsString = events.map((event) {
+ if (event == null) {
+ return 'x Stream closed.';
+ } else if (event.isValue) {
+ return addBullet(event.asValue!.value.toString());
+ } else {
+ var error = event.asError!;
+ var chain = TestHandle.current.formatStackTrace(error.stackTrace);
+ var text = '${error.error}\n$chain';
+ return indent(text, first: '! ');
+ }
+ }).join('\n');
+ if (eventsString.isEmpty) eventsString = 'no events';
+
+ transaction.reject();
+
+ var buffer = StringBuffer();
+ buffer.writeln(indent(eventsString, first: 'emitted '));
+ if (result.isNotEmpty) buffer.writeln(indent(result, first: ' which '));
+ return buffer.toString().trimRight();
+ }, onError: (Object error) {
+ transaction.reject();
+ // ignore: only_throw_errors
+ throw error;
+ }).then((result) {
+ if (shouldCancelQueue) queue.cancel();
+ return result;
+ });
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('should ').add(this.description);
+}
+
+void _unawaited(Future<void> f) {}
diff --git a/pkgs/matcher/lib/src/expect/stream_matchers.dart b/pkgs/matcher/lib/src/expect/stream_matchers.dart
new file mode 100644
index 0000000..02efff3
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/stream_matchers.dart
@@ -0,0 +1,377 @@
+// 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.
+
+import 'package:async/async.dart';
+
+import '../description.dart';
+import '../interfaces.dart';
+import '../util.dart';
+import 'async_matcher.dart';
+import 'stream_matcher.dart';
+import 'throws_matcher.dart';
+import 'util/pretty_print.dart';
+
+/// Returns a [StreamMatcher] that asserts that the stream emits a "done" event.
+final emitsDone = StreamMatcher(
+ (queue) async => (await queue.hasNext) ? '' : null, 'be done');
+
+/// Returns a [StreamMatcher] for [matcher].
+///
+/// If [matcher] is already a [StreamMatcher], it's returned as-is. If it's any
+/// other [Matcher], this matches a single event that matches that matcher. If
+/// it's any other Object, this matches a single event that's equal to that
+/// object.
+///
+/// This functions like [wrapMatcher] for [StreamMatcher]s: it can convert any
+/// matcher-like value into a proper [StreamMatcher].
+StreamMatcher emits(Object? matcher) {
+ if (matcher is StreamMatcher) return matcher;
+ var wrapped = wrapMatcher(matcher);
+
+ var matcherDescription = wrapped.describe(StringDescription());
+
+ return StreamMatcher((queue) async {
+ if (!await queue.hasNext) return '';
+
+ var matchState = {};
+ var actual = await queue.next;
+ if (wrapped.matches(actual, matchState)) return null;
+
+ var mismatchDescription = StringDescription();
+ wrapped.describeMismatch(actual, mismatchDescription, matchState, false);
+
+ if (mismatchDescription.length == 0) return '';
+ return 'emitted an event that $mismatchDescription';
+ },
+ // TODO(nweiz): add "should" once matcher#42 is fixed.
+ 'emit an event that $matcherDescription');
+}
+
+/// Returns a [StreamMatcher] that matches a single error event that matches
+/// [matcher].
+StreamMatcher emitsError(Object? matcher) {
+ var wrapped = wrapMatcher(matcher);
+ var matcherDescription = wrapped.describe(StringDescription());
+ var throwsMatcher = throwsA(wrapped) as AsyncMatcher;
+
+ return StreamMatcher(
+ (queue) => throwsMatcher.matchAsync(queue.next) as Future<String?>,
+ // TODO(nweiz): add "should" once matcher#42 is fixed.
+ 'emit an error that $matcherDescription');
+}
+
+/// Returns a [StreamMatcher] that allows (but doesn't require) [matcher] to
+/// match the stream.
+///
+/// This matcher always succeeds; if [matcher] doesn't match, this just consumes
+/// no events.
+StreamMatcher mayEmit(Object? matcher) {
+ var streamMatcher = emits(matcher);
+ return StreamMatcher((queue) async {
+ await queue.withTransaction(
+ (copy) async => (await streamMatcher.matchQueue(copy)) == null);
+ return null;
+ }, 'maybe ${streamMatcher.description}');
+}
+
+/// Returns a [StreamMatcher] that matches the stream if at least one of
+/// [matchers] matches.
+///
+/// If multiple matchers match the stream, this chooses the matcher that
+/// consumes as many events as possible.
+///
+/// If any matchers match the stream, no errors from other matchers are thrown.
+/// If no matchers match and multiple matchers threw errors, the first error is
+/// re-thrown.
+StreamMatcher emitsAnyOf(Iterable matchers) {
+ var streamMatchers = matchers.map(emits).toList();
+ if (streamMatchers.isEmpty) {
+ throw ArgumentError('matcher may not be empty');
+ }
+
+ if (streamMatchers.length == 1) return streamMatchers.first;
+ var description = 'do one of the following:\n'
+ '${bullet(streamMatchers.map((matcher) => matcher.description))}';
+
+ return StreamMatcher((queue) async {
+ var transaction = queue.startTransaction();
+
+ // Allocate the failures list ahead of time so that its order matches the
+ // order of [matchers], and thus the order the matchers will be listed in
+ // the description.
+ var failures = List<String?>.filled(matchers.length, null);
+
+ // The first error thrown. If no matchers match and this exists, we rethrow
+ // it.
+ Object? firstError;
+ StackTrace? firstStackTrace;
+
+ var futures = <Future>[];
+ StreamQueue? consumedMost;
+ for (var i = 0; i < matchers.length; i++) {
+ futures.add(() async {
+ var copy = transaction.newQueue();
+
+ String? result;
+ try {
+ result = await streamMatchers[i].matchQueue(copy);
+ } catch (error, stackTrace) {
+ if (firstError == null) {
+ firstError = error;
+ firstStackTrace = stackTrace;
+ }
+ return;
+ }
+
+ if (result != null) {
+ failures[i] = result;
+ } else if (consumedMost == null ||
+ consumedMost!.eventsDispatched < copy.eventsDispatched) {
+ consumedMost = copy;
+ }
+ }());
+ }
+
+ await Future.wait(futures);
+
+ if (consumedMost == null) {
+ transaction.reject();
+ if (firstError != null) {
+ await Future.error(firstError!, firstStackTrace);
+ }
+
+ var failureMessages = <String>[];
+ for (var i = 0; i < matchers.length; i++) {
+ var message = 'failed to ${streamMatchers[i].description}';
+ if (failures[i]!.isNotEmpty) {
+ message += message.contains('\n') ? '\n' : ' ';
+ message += 'because it ${failures[i]}';
+ }
+
+ failureMessages.add(message);
+ }
+
+ return 'failed all options:\n${bullet(failureMessages)}';
+ } else {
+ transaction.commit(consumedMost!);
+ return null;
+ }
+ }, description);
+}
+
+/// Returns a [StreamMatcher] that matches the stream if each matcher in
+/// [matchers] matches, one after another.
+///
+/// If any matcher fails to match, this fails and consumes no events.
+StreamMatcher emitsInOrder(Iterable matchers) {
+ var streamMatchers = matchers.map(emits).toList();
+ if (streamMatchers.length == 1) return streamMatchers.first;
+
+ var description = 'do the following in order:\n'
+ '${bullet(streamMatchers.map((matcher) => matcher.description))}';
+
+ return StreamMatcher((queue) async {
+ for (var i = 0; i < streamMatchers.length; i++) {
+ var matcher = streamMatchers[i];
+ var result = await matcher.matchQueue(queue);
+ if (result == null) continue;
+
+ var newResult = "didn't ${matcher.description}";
+ if (result.isNotEmpty) {
+ newResult += newResult.contains('\n') ? '\n' : ' ';
+ newResult += 'because it $result';
+ }
+ return newResult;
+ }
+ return null;
+ }, description);
+}
+
+/// Returns a [StreamMatcher] that matches any number of events followed by
+/// events that match [matcher].
+///
+/// This consumes all events matched by [matcher], as well as all events before.
+/// If the stream emits a done event without matching [matcher], this fails and
+/// consumes no events.
+StreamMatcher emitsThrough(Object? matcher) {
+ var streamMatcher = emits(matcher);
+ return StreamMatcher((queue) async {
+ var failures = <String>[];
+
+ Future<bool> tryHere() => queue.withTransaction((copy) async {
+ var result = await streamMatcher.matchQueue(copy);
+ if (result == null) return true;
+ failures.add(result);
+ return false;
+ });
+
+ while (await queue.hasNext) {
+ if (await tryHere()) return null;
+ await queue.next;
+ }
+
+ // Try after the queue is done in case the matcher can match an empty
+ // stream.
+ if (await tryHere()) return null;
+
+ var result = 'never did ${streamMatcher.description}';
+
+ var failureMessages =
+ bullet(failures.where((failure) => failure.isNotEmpty));
+ if (failureMessages.isNotEmpty) {
+ result += result.contains('\n') ? '\n' : ' ';
+ result += 'because it:\n$failureMessages';
+ }
+
+ return result;
+ }, 'eventually ${streamMatcher.description}');
+}
+
+/// Returns a [StreamMatcher] that matches any number of events that match
+/// [matcher].
+///
+/// This consumes events until [matcher] no longer matches. It always succeeds;
+/// if [matcher] doesn't match, this just consumes no events. It never rethrows
+/// errors.
+StreamMatcher mayEmitMultiple(Object? matcher) {
+ var streamMatcher = emits(matcher);
+
+ var description = streamMatcher.description;
+ description += description.contains('\n') ? '\n' : ' ';
+ description += 'zero or more times';
+
+ return StreamMatcher((queue) async {
+ while (await _tryMatch(queue, streamMatcher)) {
+ // Do nothing; the matcher presumably already consumed events.
+ }
+ return null;
+ }, description);
+}
+
+/// Returns a [StreamMatcher] that matches a stream that never matches
+/// [matcher].
+///
+/// This doesn't complete until the stream emits a done event. It never consumes
+/// any events. It never re-throws errors.
+StreamMatcher neverEmits(Object? matcher) {
+ var streamMatcher = emits(matcher);
+ return StreamMatcher((queue) async {
+ var events = 0;
+ var matched = false;
+ await queue.withTransaction((copy) async {
+ while (await copy.hasNext) {
+ matched = await _tryMatch(copy, streamMatcher);
+ if (matched) return false;
+
+ events++;
+
+ try {
+ await copy.next;
+ } catch (_) {
+ // Ignore errors events.
+ }
+ }
+
+ matched = await _tryMatch(copy, streamMatcher);
+ return false;
+ });
+
+ if (!matched) return null;
+ return "after $events ${pluralize('event', events)} did "
+ '${streamMatcher.description}';
+ }, 'never ${streamMatcher.description}');
+}
+
+/// Returns whether [matcher] matches [queue] at its current position.
+///
+/// This treats errors as failures to match.
+Future<bool> _tryMatch(StreamQueue queue, StreamMatcher matcher) {
+ return queue.withTransaction((copy) async {
+ try {
+ return (await matcher.matchQueue(copy)) == null;
+ } catch (_) {
+ return false;
+ }
+ });
+}
+
+/// Returns a [StreamMatcher] that matches the stream if each matcher in
+/// [matchers] matches, in any order.
+///
+/// If any matcher fails to match, this fails and consumes no events. If the
+/// matchers match in multiple different possible orders, this chooses the order
+/// that consumes as many events as possible.
+///
+/// If any sequence of matchers matches the stream, no errors from other
+/// sequences are thrown. If no sequences match and multiple sequences throw
+/// errors, the first error is re-thrown.
+///
+/// Note that checking every ordering of [matchers] is O(n!) in the worst case,
+/// so this should only be called when there are very few [matchers].
+StreamMatcher emitsInAnyOrder(Iterable matchers) {
+ var streamMatchers = matchers.map(emits).toSet();
+ if (streamMatchers.length == 1) return streamMatchers.first;
+ var description = 'do the following in any order:\n'
+ '${bullet(streamMatchers.map((matcher) => matcher.description))}';
+
+ return StreamMatcher(
+ (queue) async => await _tryInAnyOrder(queue, streamMatchers) ? null : '',
+ description);
+}
+
+/// Returns whether [queue] matches [matchers] in any order.
+Future<bool> _tryInAnyOrder(
+ StreamQueue queue, Set<StreamMatcher> matchers) async {
+ if (matchers.length == 1) {
+ return await matchers.first.matchQueue(queue) == null;
+ }
+
+ var transaction = queue.startTransaction();
+ StreamQueue? consumedMost;
+
+ // The first error thrown. If no matchers match and this exists, we rethrow
+ // it.
+ Object? firstError;
+ StackTrace? firstStackTrace;
+
+ await Future.wait(matchers.map((matcher) async {
+ var copy = transaction.newQueue();
+ try {
+ if (await matcher.matchQueue(copy) != null) return;
+ } catch (error, stackTrace) {
+ if (firstError == null) {
+ firstError = error;
+ firstStackTrace = stackTrace;
+ }
+ return;
+ }
+
+ var rest = Set<StreamMatcher>.from(matchers);
+ rest.remove(matcher);
+
+ try {
+ if (!await _tryInAnyOrder(copy, rest)) return;
+ } catch (error, stackTrace) {
+ if (firstError == null) {
+ firstError = error;
+ firstStackTrace = stackTrace;
+ }
+ return;
+ }
+
+ if (consumedMost == null ||
+ consumedMost!.eventsDispatched < copy.eventsDispatched) {
+ consumedMost = copy;
+ }
+ }));
+
+ if (consumedMost == null) {
+ transaction.reject();
+ if (firstError != null) await Future.error(firstError!, firstStackTrace);
+ return false;
+ } else {
+ transaction.commit(consumedMost!);
+ return true;
+ }
+}
diff --git a/pkgs/matcher/lib/src/expect/throws_matcher.dart b/pkgs/matcher/lib/src/expect/throws_matcher.dart
new file mode 100644
index 0000000..37676ef
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/throws_matcher.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2014, 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+import 'package:test_api/hooks.dart';
+
+import '../description.dart';
+import '../interfaces.dart';
+import '../util.dart';
+import 'async_matcher.dart';
+import 'util/pretty_print.dart';
+
+/// This function is deprecated.
+///
+/// Use [throwsA] instead. We strongly recommend that you add assertions about
+/// at least the type of the error, but you can write `throwsA(anything)` to
+/// mimic the behavior of this matcher.
+@Deprecated('Will be removed in 0.13.0')
+const Matcher throws = Throws();
+
+/// This can be used to match three kinds of objects:
+///
+/// * A [Function] that throws an exception when called. The function cannot
+/// take any arguments. If you want to test that a function expecting
+/// arguments throws, wrap it in another zero-argument function that calls
+/// the one you want to test.
+///
+/// * A [Future] that completes with an exception. Note that this creates an
+/// asynchronous expectation. The call to `expect()` that includes this will
+/// return immediately and execution will continue. Later, when the future
+/// completes, the actual expectation will run.
+///
+/// * A [Function] that returns a [Future] that completes with an exception.
+///
+/// In all three cases, when an exception is thrown, this will test that the
+/// exception object matches [matcher]. If [matcher] is not an instance of
+/// [Matcher], it will implicitly be treated as `equals(matcher)`.
+///
+/// Examples:
+/// ```dart
+/// void functionThatThrows() => throw SomeException();
+///
+/// void functionWithArgument(bool shouldThrow) {
+/// if (shouldThrow) {
+/// throw SomeException();
+/// }
+/// }
+///
+/// Future<void> asyncFunctionThatThrows() async => throw SomeException();
+///
+/// expect(functionThatThrows, throwsA(isA<SomeException>()));
+///
+/// expect(() => functionWithArgument(true), throwsA(isA<SomeException>()));
+///
+/// var future = asyncFunctionThatThrows();
+/// await expectLater(future, throwsA(isA<SomeException>()));
+///
+/// await expectLater(
+/// asyncFunctionThatThrows, throwsA(isA<SomeException>()));
+/// ```
+Matcher throwsA(Object? matcher) => Throws(wrapMatcher(matcher));
+
+/// Use the [throwsA] function instead.
+@Deprecated('Will be removed in 0.13.0')
+class Throws extends AsyncMatcher {
+ final Matcher? _matcher;
+
+ const Throws([Matcher? matcher]) : _matcher = matcher;
+
+ // Avoid async/await so we synchronously fail if we match a synchronous
+ // function.
+ @override
+ dynamic /*FutureOr<String>*/ matchAsync(Object? item) {
+ if (item is! Function && item is! Future) {
+ return 'was not a Function or Future';
+ }
+
+ if (item is Future) {
+ return _matchFuture(item, 'emitted ');
+ }
+
+ try {
+ var value = (item as Function)();
+ if (value is Future) {
+ return _matchFuture(value, 'returned a Future that emitted ');
+ }
+
+ return indent(prettyPrint(value), first: 'returned ');
+ } catch (error, trace) {
+ return _check(error, trace);
+ }
+ }
+
+ /// Matches [future], using try/catch since `onError` doesn't seem to work
+ /// properly in nnbd.
+ Future<String?> _matchFuture(
+ Future<dynamic> future, String messagePrefix) async {
+ try {
+ var value = await future;
+ return indent(prettyPrint(value), first: messagePrefix);
+ } catch (error, trace) {
+ return _check(error, trace);
+ }
+ }
+
+ @override
+ Description describe(Description description) {
+ if (_matcher == null) {
+ return description.add('throws');
+ } else {
+ return description.add('throws ').addDescriptionOf(_matcher);
+ }
+ }
+
+ /// Verifies that [error] matches [_matcher] and returns a [String]
+ /// description of the failure if it doesn't.
+ String? _check(error, StackTrace? trace) {
+ if (_matcher == null) return null;
+
+ var matchState = {};
+ if (_matcher.matches(error, matchState)) return null;
+
+ var result = _matcher
+ .describeMismatch(error, StringDescription(), matchState, false)
+ .toString();
+
+ var buffer = StringBuffer();
+ buffer.writeln(indent(prettyPrint(error), first: 'threw '));
+ if (trace != null) {
+ buffer.writeln(indent(
+ TestHandle.current.formatStackTrace(trace).toString(),
+ first: 'stack '));
+ }
+ if (result.isNotEmpty) buffer.writeln(indent(result, first: 'which '));
+ return buffer.toString().trimRight();
+ }
+}
diff --git a/pkgs/matcher/lib/src/expect/throws_matchers.dart b/pkgs/matcher/lib/src/expect/throws_matchers.dart
new file mode 100644
index 0000000..67d35b7
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/throws_matchers.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2014, 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+import '../error_matchers.dart';
+import '../interfaces.dart';
+import '../type_matcher.dart';
+import 'throws_matcher.dart';
+
+/// A matcher for functions that throw ArgumentError.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsArgumentError = Throws(isArgumentError);
+
+/// A matcher for functions that throw ConcurrentModificationError.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsConcurrentModificationError =
+ Throws(isConcurrentModificationError);
+
+/// A matcher for functions that throw CyclicInitializationError.
+///
+/// See [throwsA] for objects that this can be matched against.
+@Deprecated('throwsCyclicInitializationError has been deprecated, because '
+ 'the type will longer exists in Dart 3.0. It will now catch any kind of '
+ 'error, not only CyclicInitializationError.')
+const Matcher throwsCyclicInitializationError = Throws(TypeMatcher<Error>());
+
+/// A matcher for functions that throw Exception.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsException = Throws(isException);
+
+/// A matcher for functions that throw FormatException.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsFormatException = Throws(isFormatException);
+
+/// A matcher for functions that throw NoSuchMethodError.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsNoSuchMethodError = Throws(isNoSuchMethodError);
+
+/// A matcher for functions that throw NullThrownError.
+///
+/// See [throwsA] for objects that this can be matched against.
+@Deprecated('throwsNullThrownError has been deprecated, because '
+ 'NullThrownError has been replaced with TypeError. '
+ 'Use `throwsA(isA<TypeError>())` instead.')
+const Matcher throwsNullThrownError = Throws(TypeMatcher<TypeError>());
+
+/// A matcher for functions that throw RangeError.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsRangeError = Throws(isRangeError);
+
+/// A matcher for functions that throw StateError.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsStateError = Throws(isStateError);
+
+/// A matcher for functions that throw Exception.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsUnimplementedError = Throws(isUnimplementedError);
+
+/// A matcher for functions that throw UnsupportedError.
+///
+/// See [throwsA] for objects that this can be matched against.
+const Matcher throwsUnsupportedError = Throws(isUnsupportedError);
diff --git a/pkgs/matcher/lib/src/expect/util/placeholder.dart b/pkgs/matcher/lib/src/expect/util/placeholder.dart
new file mode 100644
index 0000000..ee2dc70
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/util/placeholder.dart
@@ -0,0 +1,16 @@
+// 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.
+
+/// A class that's used as a default argument to detect whether an argument was
+/// passed.
+///
+/// We use a custom class for this rather than just `const Object()` so that
+/// callers can't accidentally pass the placeholder value.
+class _Placeholder {
+ const _Placeholder();
+}
+
+/// A placeholder to use as a default argument value to detect whether an
+/// argument was passed.
+const placeholder = _Placeholder();
diff --git a/pkgs/matcher/lib/src/expect/util/pretty_print.dart b/pkgs/matcher/lib/src/expect/util/pretty_print.dart
new file mode 100644
index 0000000..de635ba
--- /dev/null
+++ b/pkgs/matcher/lib/src/expect/util/pretty_print.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2021, 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 'package:term_glyph/term_glyph.dart' as glyph;
+
+import '../../description.dart';
+
+/// Indent each line in [text] by [first] spaces.
+///
+/// [first] is used in place of the first line's indentation.
+String indent(String text, {required String first}) {
+ final prefix = ' ' * first.length;
+ var lines = text.split('\n');
+ if (lines.length == 1) return '$first$text';
+
+ var buffer = StringBuffer('$first${lines.first}\n');
+
+ // Write out all but the first and last lines with [prefix].
+ for (var line in lines.skip(1).take(lines.length - 2)) {
+ buffer.writeln('$prefix$line');
+ }
+ buffer.write('$prefix${lines.last}');
+ return buffer.toString();
+}
+
+/// Returns a pretty-printed representation of [value].
+///
+/// The matcher package doesn't expose its pretty-print function directly, but
+/// we can use it through StringDescription.
+String prettyPrint(Object? value) =>
+ StringDescription().addDescriptionOf(value).toString();
+
+/// Indents [text], and adds a bullet at the beginning.
+String addBullet(String text) => indent(text, first: '${glyph.bullet} ');
+
+/// Converts [strings] to a bulleted list.
+String bullet(Iterable<String> strings) => strings.map(addBullet).join('\n');
+
+/// Returns [name] if [number] is 1, or the plural of [name] otherwise.
+///
+/// By default, this just adds "s" to the end of [name] to get the plural. If
+/// [plural] is passed, that's used instead.
+String pluralize(String name, int number, {String? plural}) {
+ if (number == 1) return name;
+ if (plural != null) return plural;
+ return '${name}s';
+}
diff --git a/pkgs/matcher/lib/src/feature_matcher.dart b/pkgs/matcher/lib/src/feature_matcher.dart
new file mode 100644
index 0000000..1dbe56c
--- /dev/null
+++ b/pkgs/matcher/lib/src/feature_matcher.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2018, 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 'interfaces.dart';
+import 'type_matcher.dart';
+
+/// A package-private [TypeMatcher] implementation that makes it easy for
+/// subclasses to validate aspects of specific types while providing consistent
+/// type checking.
+abstract class FeatureMatcher<T> extends TypeMatcher<T> {
+ const FeatureMatcher();
+
+ @override
+ bool matches(dynamic item, Map matchState) =>
+ super.matches(item, matchState) && typedMatches(item as T, matchState);
+
+ bool typedMatches(T item, Map matchState);
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ if (item is T) {
+ return describeTypedMismatch(
+ item, mismatchDescription, matchState, verbose);
+ }
+
+ return super.describe(mismatchDescription.add('not an '));
+ }
+
+ Description describeTypedMismatch(T item, Description mismatchDescription,
+ Map matchState, bool verbose) =>
+ mismatchDescription;
+}
diff --git a/pkgs/matcher/lib/src/having_matcher.dart b/pkgs/matcher/lib/src/having_matcher.dart
new file mode 100644
index 0000000..2d2dc3d
--- /dev/null
+++ b/pkgs/matcher/lib/src/having_matcher.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2018, 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 'custom_matcher.dart';
+import 'interfaces.dart';
+import 'type_matcher.dart';
+import 'util.dart';
+
+/// A package-private [TypeMatcher] implementation that handles is returned
+/// by calls to [TypeMatcher.having].
+class HavingMatcher<T> implements TypeMatcher<T> {
+ final TypeMatcher<T> _parent;
+ final List<_FunctionMatcher<T>> _functionMatchers;
+
+ HavingMatcher(this._parent, String description, Object? Function(T) feature,
+ dynamic matcher)
+ : _functionMatchers = [
+ _FunctionMatcher<T>(description, feature, matcher)
+ ];
+
+ HavingMatcher._fromExisting(
+ this._parent,
+ String description,
+ Object? Function(T) feature,
+ dynamic matcher,
+ Iterable<_FunctionMatcher<T>>? existing)
+ : _functionMatchers = [
+ ...?existing,
+ _FunctionMatcher<T>(description, feature, matcher)
+ ];
+
+ @override
+ TypeMatcher<T> having(
+ Object? Function(T) feature, String description, dynamic matcher) =>
+ HavingMatcher._fromExisting(
+ _parent, description, feature, matcher, _functionMatchers);
+
+ @override
+ bool matches(dynamic item, Map matchState) {
+ for (var matcher in <Matcher>[_parent].followedBy(_functionMatchers)) {
+ if (!matcher.matches(item, matchState)) {
+ addStateInfo(matchState, {'matcher': matcher});
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ var matcher = matchState['matcher'] as Matcher;
+ matcher.describeMismatch(
+ item, mismatchDescription, matchState['state'] as Map, verbose);
+ return mismatchDescription;
+ }
+
+ @override
+ Description describe(Description description) => description
+ .add('')
+ .addDescriptionOf(_parent)
+ .add(' with ')
+ .addAll('', ' and ', '', _functionMatchers);
+}
+
+class _FunctionMatcher<T> extends CustomMatcher {
+ final Object? Function(T value) _feature;
+
+ _FunctionMatcher(String name, this._feature, Object? matcher)
+ : super('`$name`:', '`$name`', matcher);
+
+ @override
+ Object? featureValueOf(covariant T actual) => _feature(actual);
+}
diff --git a/pkgs/matcher/lib/src/interfaces.dart b/pkgs/matcher/lib/src/interfaces.dart
new file mode 100644
index 0000000..24527f7
--- /dev/null
+++ b/pkgs/matcher/lib/src/interfaces.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2012, 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.
+
+/// Matchers build up their error messages by appending to Description objects.
+///
+/// This interface is implemented by StringDescription.
+///
+/// This interface is unlikely to need other implementations, but could be
+/// useful to replace in some cases - e.g. language conversion.
+abstract class Description {
+ int get length;
+
+ /// Change the value of the description.
+ Description replace(String text);
+
+ /// This is used to add arbitrary text to the description.
+ Description add(String text);
+
+ /// This is used to add a meaningful description of a value.
+ Description addDescriptionOf(Object? value);
+
+ /// This is used to add a description of an [Iterable] [list],
+ /// with appropriate [start] and [end] markers and inter-element [separator].
+ Description addAll(String start, String separator, String end, Iterable list);
+}
+
+/// The base class for all matchers.
+///
+/// [matches] and [describe] must be implemented by subclasses.
+///
+/// Subclasses can override [describeMismatch] if a more specific description is
+/// required when the matcher fails.
+abstract class Matcher {
+ const Matcher();
+
+ /// Does the matching of the actual vs expected values.
+ ///
+ /// [item] is the actual value. [matchState] can be supplied
+ /// and may be used to add details about the mismatch that are too
+ /// costly to determine in [describeMismatch].
+ bool matches(dynamic item, Map matchState);
+
+ /// Builds a textual description of the matcher.
+ Description describe(Description description);
+
+ /// Builds a textual description of a specific mismatch.
+ ///
+ /// [item] is the value that was tested by [matches]; [matchState] is
+ /// the [Map] that was passed to and supplemented by [matches]
+ /// with additional information about the mismatch, and [mismatchDescription]
+ /// is the [Description] that is being built to describe the mismatch.
+ ///
+ /// A few matchers make use of the [verbose] flag to provide detailed
+ /// information that is not typically included but can be of help in
+ /// diagnosing failures, such as stack traces.
+ Description describeMismatch(dynamic item, Description mismatchDescription,
+ Map matchState, bool verbose) =>
+ mismatchDescription;
+}
diff --git a/pkgs/matcher/lib/src/iterable_matchers.dart b/pkgs/matcher/lib/src/iterable_matchers.dart
new file mode 100644
index 0000000..abc9688
--- /dev/null
+++ b/pkgs/matcher/lib/src/iterable_matchers.dart
@@ -0,0 +1,416 @@
+// Copyright (c) 2012, 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 'description.dart';
+import 'equals_matcher.dart';
+import 'feature_matcher.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher which matches [Iterable]s in which all elements
+/// match the given [valueOrMatcher].
+Matcher everyElement(Object? valueOrMatcher) =>
+ _EveryElement(wrapMatcher(valueOrMatcher));
+
+class _EveryElement extends _IterableMatcher {
+ final Matcher _matcher;
+
+ _EveryElement(this._matcher);
+
+ @override
+ bool typedMatches(Iterable item, Map matchState) {
+ var i = 0;
+ for (var element in item) {
+ if (!_matcher.matches(element, matchState)) {
+ addStateInfo(matchState, {'index': i, 'element': element});
+ return false;
+ }
+ ++i;
+ }
+ return true;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('every element(').addDescriptionOf(_matcher).add(')');
+
+ @override
+ Description describeTypedMismatch(dynamic item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ if (matchState['index'] != null) {
+ var index = matchState['index'];
+ var element = matchState['element'];
+ mismatchDescription
+ .add('has value ')
+ .addDescriptionOf(element)
+ .add(' which ');
+ var subDescription = StringDescription();
+ _matcher.describeMismatch(
+ element, subDescription, matchState['state'] as Map, verbose);
+ if (subDescription.length > 0) {
+ mismatchDescription.add(subDescription.toString());
+ } else {
+ mismatchDescription.add("doesn't match ");
+ _matcher.describe(mismatchDescription);
+ }
+ mismatchDescription.add(' at index $index');
+ return mismatchDescription;
+ }
+ return super
+ .describeMismatch(item, mismatchDescription, matchState, verbose);
+ }
+}
+
+/// Returns a matcher which matches [Iterable]s in which at least one
+/// element matches the given [valueOrMatcher].
+Matcher anyElement(Object? valueOrMatcher) =>
+ _AnyElement(wrapMatcher(valueOrMatcher));
+
+class _AnyElement extends _IterableMatcher {
+ final Matcher _matcher;
+
+ _AnyElement(this._matcher);
+
+ @override
+ bool typedMatches(Iterable item, Map matchState) =>
+ item.any((e) => _matcher.matches(e, matchState));
+
+ @override
+ Description describe(Description description) =>
+ description.add('some element ').addDescriptionOf(_matcher);
+}
+
+/// Returns a matcher which matches [Iterable]s that have the same
+/// length and the same elements as [expected], in the same order.
+///
+/// This is equivalent to [equals] but does not recurse.
+Matcher orderedEquals(Iterable expected) => _OrderedEquals(expected);
+
+class _OrderedEquals extends _IterableMatcher {
+ final Iterable _expected;
+ final Matcher _matcher;
+
+ _OrderedEquals(this._expected) : _matcher = equals(_expected, 1);
+
+ @override
+ bool typedMatches(Iterable item, Map matchState) =>
+ _matcher.matches(item, matchState);
+
+ @override
+ Description describe(Description description) =>
+ description.add('equals ').addDescriptionOf(_expected).add(' ordered');
+
+ @override
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ return _matcher.describeMismatch(
+ item, mismatchDescription, matchState, verbose);
+ }
+}
+
+/// Returns a matcher which matches [Iterable]s that have the same length and
+/// the same elements as [expected], but not necessarily in the same order.
+///
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
+Matcher unorderedEquals(Iterable expected) => _UnorderedEquals(expected);
+
+class _UnorderedEquals extends _UnorderedMatches {
+ final List _expectedValues;
+
+ _UnorderedEquals(Iterable expected)
+ : _expectedValues = expected.toList(),
+ super(expected.map(equals));
+
+ @override
+ Description describe(Description description) => description
+ .add('equals ')
+ .addDescriptionOf(_expectedValues)
+ .add(' unordered');
+}
+
+/// Iterable matchers match against [Iterable]s. We add this intermediate
+/// class to give better mismatch error messages than the base Matcher class.
+abstract class _IterableMatcher extends FeatureMatcher<Iterable> {
+ const _IterableMatcher();
+}
+
+/// Returns a matcher which matches [Iterable]s whose elements match the
+/// matchers in [expected], but not necessarily in the same order.
+///
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
+Matcher unorderedMatches(Iterable expected) => _UnorderedMatches(expected);
+
+class _UnorderedMatches extends _IterableMatcher {
+ final List<Matcher> _expected;
+ final bool _allowUnmatchedValues;
+
+ _UnorderedMatches(Iterable expected, {bool allowUnmatchedValues = false})
+ : _expected = expected.map(wrapMatcher).toList(),
+ _allowUnmatchedValues = allowUnmatchedValues;
+
+ String? _test(List values) {
+ // Check the lengths are the same.
+ if (_expected.length > values.length) {
+ return 'has too few elements (${values.length} < ${_expected.length})';
+ } else if (!_allowUnmatchedValues && _expected.length < values.length) {
+ return 'has too many elements (${values.length} > ${_expected.length})';
+ }
+
+ var edges = List.generate(values.length, (_) => <int>[], growable: false);
+ for (var v = 0; v < values.length; v++) {
+ for (var m = 0; m < _expected.length; m++) {
+ if (_expected[m].matches(values[v], {})) {
+ edges[v].add(m);
+ }
+ }
+ }
+ // The index into `values` matched with each matcher or `null` if no value
+ // has been matched yet.
+ var matched = List<int?>.filled(_expected.length, null);
+ for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
+ _findPairing(edges, valueIndex, matched);
+ }
+ for (var matcherIndex = 0;
+ matcherIndex < _expected.length;
+ matcherIndex++) {
+ if (matched[matcherIndex] == null) {
+ final description = StringDescription()
+ .add('has no match for ')
+ .addDescriptionOf(_expected[matcherIndex])
+ .add(' at index $matcherIndex');
+ final remainingUnmatched =
+ matched.sublist(matcherIndex + 1).where((m) => m == null).length;
+ return remainingUnmatched == 0
+ ? description.toString()
+ : description
+ .add(' along with $remainingUnmatched other unmatched')
+ .toString();
+ }
+ }
+ return null;
+ }
+
+ @override
+ bool typedMatches(Iterable item, Map mismatchState) =>
+ _test(item.toList()) == null;
+
+ @override
+ Description describe(Description description) => description
+ .add('matches ')
+ .addAll('[', ', ', ']', _expected)
+ .add(' unordered');
+
+ @override
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) =>
+ mismatchDescription.add(_test(item.toList())!);
+
+ /// Returns `true` if the value at [valueIndex] can be paired with some
+ /// unmatched matcher and updates the state of [matched].
+ ///
+ /// If there is a conflict where multiple values may match the same matcher
+ /// recursively looks for a new place to match the old value.
+ bool _findPairing(
+ List<List<int>> edges, int valueIndex, List<int?> matched) =>
+ _findPairingInner(edges, valueIndex, matched, <int>{});
+
+ /// Implementation of [_findPairing], tracks [reserved] which are the
+ /// matchers that have been used _during_ this search.
+ bool _findPairingInner(List<List<int>> edges, int valueIndex,
+ List<int?> matched, Set<int> reserved) {
+ final possiblePairings =
+ edges[valueIndex].where((m) => !reserved.contains(m));
+ for (final matcherIndex in possiblePairings) {
+ reserved.add(matcherIndex);
+ final previouslyMatched = matched[matcherIndex];
+ if (previouslyMatched == null ||
+ // If the matcher isn't already free, check whether the existing value
+ // occupying the matcher can be bumped to another one.
+ _findPairingInner(edges, matched[matcherIndex]!, matched, reserved)) {
+ matched[matcherIndex] = valueIndex;
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+/// A pairwise matcher for [Iterable]s.
+///
+/// The [comparator] function, taking an expected and an actual argument, and
+/// returning whether they match, will be applied to each pair in order.
+/// [description] should be a meaningful name for the comparator.
+Matcher pairwiseCompare<S, T>(Iterable<S> expected,
+ bool Function(S, T) comparator, String description) =>
+ _PairwiseCompare(expected, comparator, description);
+
+typedef _Comparator<S, T> = bool Function(S a, T b);
+
+class _PairwiseCompare<S, T> extends _IterableMatcher {
+ final Iterable<S> _expected;
+ final _Comparator<S, T> _comparator;
+ final String _description;
+
+ _PairwiseCompare(this._expected, this._comparator, this._description);
+
+ @override
+ bool typedMatches(Iterable item, Map matchState) {
+ if (item.length != _expected.length) return false;
+ var iterator = item.iterator;
+ var i = 0;
+ for (var e in _expected) {
+ iterator.moveNext();
+ if (!_comparator(e, iterator.current as T)) {
+ addStateInfo(matchState,
+ {'index': i, 'expected': e, 'actual': iterator.current});
+ return false;
+ }
+ i++;
+ }
+ return true;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('pairwise $_description ').addDescriptionOf(_expected);
+
+ @override
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ if (item.length != _expected.length) {
+ return mismatchDescription
+ .add('has length ${item.length} instead of ${_expected.length}');
+ } else {
+ return mismatchDescription
+ .add('has ')
+ .addDescriptionOf(matchState['actual'])
+ .add(' which is not $_description ')
+ .addDescriptionOf(matchState['expected'])
+ .add(' at index ${matchState["index"]}');
+ }
+ }
+}
+
+/// Matches [Iterable]s which contain an element matching every value in
+/// [expected] in any order, and may contain additional values.
+///
+/// For example: `[0, 1, 0, 2, 0]` matches `containsAll([1, 2])` and
+/// `containsAll([2, 1])` but not `containsAll([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
+///
+/// Each element in the value will only be considered a match for a single
+/// matcher in [expected] even if it could satisfy more than one. For instance
+/// `containsAll([greaterThan(1), greaterThan(2)])` will not be satisfied by
+/// `[3]`. To check that all matchers are satisfied within an iterable and allow
+/// the same element to satisfy multiple matchers use
+/// `allOf(matchers.map(contains))`.
+///
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
+Matcher containsAll(Iterable expected) => _ContainsAll(expected);
+
+class _ContainsAll extends _UnorderedMatches {
+ final Iterable _unwrappedExpected;
+
+ _ContainsAll(Iterable expected)
+ : _unwrappedExpected = expected,
+ super(expected.map(wrapMatcher), allowUnmatchedValues: true);
+ @override
+ Description describe(Description description) =>
+ description.add('contains all of ').addDescriptionOf(_unwrappedExpected);
+}
+
+/// Matches [Iterable]s which contain an element matching every value in
+/// [expected] in the same order, but may contain additional values interleaved
+/// throughout.
+///
+/// For example: `[0, 1, 0, 2, 0]` matches `containsAllInOrder([1, 2])` but not
+/// `containsAllInOrder([2, 1])` or `containsAllInOrder([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
+Matcher containsAllInOrder(Iterable expected) => _ContainsAllInOrder(expected);
+
+class _ContainsAllInOrder extends _IterableMatcher {
+ final Iterable _expected;
+
+ _ContainsAllInOrder(this._expected);
+
+ String? _test(Iterable item, Map matchState) {
+ var matchers = _expected.map(wrapMatcher).toList();
+ var matcherIndex = 0;
+ for (var value in item) {
+ if (matchers[matcherIndex].matches(value, matchState)) matcherIndex++;
+ if (matcherIndex == matchers.length) return null;
+ }
+ return StringDescription()
+ .add('did not find a value matching ')
+ .addDescriptionOf(matchers[matcherIndex])
+ .add(' following expected prior values')
+ .toString();
+ }
+
+ @override
+ bool typedMatches(Iterable item, Map matchState) =>
+ _test(item, matchState) == null;
+
+ @override
+ Description describe(Description description) => description
+ .add('contains in order(')
+ .addDescriptionOf(_expected)
+ .add(')');
+
+ @override
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) =>
+ mismatchDescription.add(_test(item, matchState)!);
+}
+
+/// Matches [Iterable]s where exactly one element matches the expected
+/// value, and all other elements don't match.
+Matcher containsOnce(Object? expected) => _ContainsOnce(expected);
+
+class _ContainsOnce extends _IterableMatcher {
+ final Object? _expected;
+
+ _ContainsOnce(this._expected);
+
+ String? _test(Iterable item, Map matchState) {
+ var matcher = wrapMatcher(_expected);
+ var matches = [
+ for (var value in item)
+ if (matcher.matches(value, matchState)) value,
+ ];
+ if (matches.length == 1) {
+ return null;
+ }
+ if (matches.isEmpty) {
+ return StringDescription()
+ .add('did not find a value matching ')
+ .addDescriptionOf(matcher)
+ .toString();
+ }
+ return StringDescription()
+ .add('expected only one value matching ')
+ .addDescriptionOf(matcher)
+ .add(' but found multiple: ')
+ .addAll('', ', ', '', matches)
+ .toString();
+ }
+
+ @override
+ bool typedMatches(Iterable item, Map matchState) =>
+ _test(item, matchState) == null;
+
+ @override
+ Description describe(Description description) =>
+ description.add('contains once(').addDescriptionOf(_expected).add(')');
+
+ @override
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) =>
+ mismatchDescription.add(_test(item, matchState)!);
+}
diff --git a/pkgs/matcher/lib/src/map_matchers.dart b/pkgs/matcher/lib/src/map_matchers.dart
new file mode 100644
index 0000000..4476d06
--- /dev/null
+++ b/pkgs/matcher/lib/src/map_matchers.dart
@@ -0,0 +1,69 @@
+// Copyright (c) 2012, 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 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher which matches maps containing the given [value].
+Matcher containsValue(Object? value) => _ContainsValue(value);
+
+class _ContainsValue extends Matcher {
+ final Object? _value;
+
+ const _ContainsValue(this._value);
+
+ @override
+ bool matches(Object? item, Map matchState) =>
+ // ignore: avoid_dynamic_calls
+ (item as dynamic).containsValue(_value);
+ @override
+ Description describe(Description description) =>
+ description.add('contains value ').addDescriptionOf(_value);
+}
+
+/// Returns a matcher which matches maps containing the key-value pair
+/// with [key] => [valueOrMatcher].
+Matcher containsPair(Object? key, Object? valueOrMatcher) =>
+ _ContainsMapping(key, wrapMatcher(valueOrMatcher));
+
+class _ContainsMapping extends Matcher {
+ final Object? _key;
+ final Matcher _valueMatcher;
+
+ const _ContainsMapping(this._key, this._valueMatcher);
+
+ @override
+ bool matches(Object? item, Map matchState) =>
+ // ignore: avoid_dynamic_calls
+ (item as dynamic).containsKey(_key) &&
+ _valueMatcher.matches((item as dynamic)[_key], matchState);
+
+ @override
+ Description describe(Description description) {
+ return description
+ .add('contains pair ')
+ .addDescriptionOf(_key)
+ .add(' => ')
+ .addDescriptionOf(_valueMatcher);
+ }
+
+ @override
+ Description describeMismatch(Object? item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ // ignore: avoid_dynamic_calls
+ if (!((item as dynamic).containsKey(_key) as bool)) {
+ return mismatchDescription
+ .add(" doesn't contain key ")
+ .addDescriptionOf(_key);
+ } else {
+ mismatchDescription
+ .add(' contains key ')
+ .addDescriptionOf(_key)
+ .add(' but with value ');
+ _valueMatcher.describeMismatch(
+ (item as dynamic)[_key], mismatchDescription, matchState, verbose);
+ return mismatchDescription;
+ }
+ }
+}
diff --git a/pkgs/matcher/lib/src/numeric_matchers.dart b/pkgs/matcher/lib/src/numeric_matchers.dart
new file mode 100644
index 0000000..a798160
--- /dev/null
+++ b/pkgs/matcher/lib/src/numeric_matchers.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2012, 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 'feature_matcher.dart';
+import 'interfaces.dart';
+
+/// Returns a matcher which matches if the match argument is within [delta]
+/// of some [value].
+///
+/// In other words, this matches if the match argument is greater than
+/// than or equal [value]-[delta] and less than or equal to [value]+[delta].
+Matcher closeTo(num value, num delta) => _IsCloseTo(value, delta);
+
+class _IsCloseTo extends FeatureMatcher<num> {
+ final num _value, _delta;
+
+ const _IsCloseTo(this._value, this._delta);
+
+ @override
+ bool typedMatches(num item, Map matchState) {
+ var diff = item - _value;
+ if (diff < 0) diff = -diff;
+ return diff <= _delta;
+ }
+
+ @override
+ Description describe(Description description) => description
+ .add('a numeric value within ')
+ .addDescriptionOf(_delta)
+ .add(' of ')
+ .addDescriptionOf(_value);
+
+ @override
+ Description describeTypedMismatch(
+ num item, Description mismatchDescription, Map matchState, bool verbose) {
+ var diff = item - _value;
+ if (diff < 0) diff = -diff;
+ return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
+ }
+}
+
+/// Returns a matcher which matches if the match argument is greater
+/// than or equal to [low] and less than or equal to [high].
+Matcher inInclusiveRange(num low, num high) => _InRange(low, high, true, true);
+
+/// Returns a matcher which matches if the match argument is greater
+/// than [low] and less than [high].
+Matcher inExclusiveRange(num low, num high) =>
+ _InRange(low, high, false, false);
+
+/// Returns a matcher which matches if the match argument is greater
+/// than [low] and less than or equal to [high].
+Matcher inOpenClosedRange(num low, num high) =>
+ _InRange(low, high, false, true);
+
+/// Returns a matcher which matches if the match argument is greater
+/// than or equal to a [low] and less than [high].
+Matcher inClosedOpenRange(num low, num high) =>
+ _InRange(low, high, true, false);
+
+class _InRange extends FeatureMatcher<num> {
+ final num _low, _high;
+ final bool _lowMatchValue, _highMatchValue;
+
+ const _InRange(
+ this._low, this._high, this._lowMatchValue, this._highMatchValue);
+
+ @override
+ bool typedMatches(num value, Map matchState) {
+ if (value < _low || value > _high) {
+ return false;
+ }
+ if (value == _low) {
+ return _lowMatchValue;
+ }
+ if (value == _high) {
+ return _highMatchValue;
+ }
+ // Value may still be outside if range if it can't be compared.
+ return value > _low && value < _high;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.add('be in range from '
+ "$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to "
+ "$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})");
+}
diff --git a/pkgs/matcher/lib/src/operator_matchers.dart b/pkgs/matcher/lib/src/operator_matchers.dart
new file mode 100644
index 0000000..15e50ff
--- /dev/null
+++ b/pkgs/matcher/lib/src/operator_matchers.dart
@@ -0,0 +1,131 @@
+// Copyright (c) 2012, 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 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher that inverts [valueOrMatcher] to its logical negation.
+Matcher isNot(Object? valueOrMatcher) => _IsNot(wrapMatcher(valueOrMatcher));
+
+class _IsNot extends Matcher {
+ final Matcher _matcher;
+
+ const _IsNot(this._matcher);
+
+ @override
+ bool matches(dynamic item, Map matchState) =>
+ !_matcher.matches(item, matchState);
+
+ @override
+ Description describe(Description description) =>
+ description.add('not ').addDescriptionOf(_matcher);
+}
+
+/// This returns a matcher that matches if all of the matchers passed as
+/// arguments (up to 7) match.
+///
+/// Instead of passing the matchers separately they can be passed as a single
+/// List argument. Any argument that is not a matcher is implicitly wrapped in a
+/// Matcher to check for equality.
+Matcher allOf(Object? arg0,
+ [Object? arg1,
+ Object? arg2,
+ Object? arg3,
+ Object? arg4,
+ Object? arg5,
+ Object? arg6]) {
+ return _AllOf(_wrapArgs(arg0, arg1, arg2, arg3, arg4, arg5, arg6));
+}
+
+class _AllOf extends Matcher {
+ final List<Matcher> _matchers;
+
+ const _AllOf(this._matchers);
+
+ @override
+ bool matches(dynamic item, Map matchState) {
+ for (var matcher in _matchers) {
+ if (!matcher.matches(item, matchState)) {
+ addStateInfo(matchState, {'matcher': matcher});
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @override
+ Description describeMismatch(dynamic item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ var matcher = matchState['matcher'] as Matcher;
+ matcher.describeMismatch(
+ item, mismatchDescription, matchState['state'], verbose);
+ return mismatchDescription;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.addAll('(', ' and ', ')', _matchers);
+}
+
+/// Matches if any of the given matchers evaluate to true.
+///
+/// The arguments can be a set of matchers as separate parameters
+/// (up to 7), or a List of matchers.
+///
+/// The matchers are evaluated from left to right using short-circuit
+/// evaluation, so evaluation stops as soon as a matcher returns true.
+///
+/// Any argument that is not a matcher is implicitly wrapped in a
+/// Matcher to check for equality.
+Matcher anyOf(Object? arg0,
+ [Object? arg1,
+ Object? arg2,
+ Object? arg3,
+ Object? arg4,
+ Object? arg5,
+ Object? arg6]) {
+ return _AnyOf(_wrapArgs(arg0, arg1, arg2, arg3, arg4, arg5, arg6));
+}
+
+class _AnyOf extends Matcher {
+ final List<Matcher> _matchers;
+
+ const _AnyOf(this._matchers);
+
+ @override
+ bool matches(dynamic item, Map matchState) {
+ for (var matcher in _matchers) {
+ if (matcher.matches(item, matchState)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @override
+ Description describe(Description description) =>
+ description.addAll('(', ' or ', ')', _matchers);
+}
+
+List<Matcher> _wrapArgs(Object? arg0, Object? arg1, Object? arg2, Object? arg3,
+ Object? arg4, Object? arg5, Object? arg6) {
+ Iterable args;
+ if (arg0 is List) {
+ if (arg1 != null ||
+ arg2 != null ||
+ arg3 != null ||
+ arg4 != null ||
+ arg5 != null ||
+ arg6 != null) {
+ throw ArgumentError('If arg0 is a List, all other arguments must be'
+ ' null.');
+ }
+
+ args = arg0;
+ } else {
+ args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6].where((e) => e != null);
+ }
+
+ return args.map(wrapMatcher).toList();
+}
diff --git a/pkgs/matcher/lib/src/order_matchers.dart b/pkgs/matcher/lib/src/order_matchers.dart
new file mode 100644
index 0000000..6fe7c76
--- /dev/null
+++ b/pkgs/matcher/lib/src/order_matchers.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2016, 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 'interfaces.dart';
+
+/// Returns a matcher which matches if the match argument is greater
+/// than the given [value].
+Matcher greaterThan(Object value) =>
+ _OrderingMatcher(value, false, false, true, 'a value greater than');
+
+/// Returns a matcher which matches if the match argument is greater
+/// than or equal to the given [value].
+Matcher greaterThanOrEqualTo(Object value) => _OrderingMatcher(
+ value, true, false, true, 'a value greater than or equal to');
+
+/// Returns a matcher which matches if the match argument is less
+/// than the given [value].
+Matcher lessThan(Object value) =>
+ _OrderingMatcher(value, false, true, false, 'a value less than');
+
+/// Returns a matcher which matches if the match argument is less
+/// than or equal to the given [value].
+Matcher lessThanOrEqualTo(Object value) =>
+ _OrderingMatcher(value, true, true, false, 'a value less than or equal to');
+
+/// A matcher which matches if the match argument is zero.
+const Matcher isZero =
+ _OrderingMatcher(0, true, false, false, 'a value equal to');
+
+/// A matcher which matches if the match argument is non-zero.
+const Matcher isNonZero =
+ _OrderingMatcher(0, false, true, true, 'a value not equal to');
+
+/// A matcher which matches if the match argument is positive.
+const Matcher isPositive =
+ _OrderingMatcher(0, false, false, true, 'a positive value', false);
+
+/// A matcher which matches if the match argument is zero or negative.
+const Matcher isNonPositive =
+ _OrderingMatcher(0, true, true, false, 'a non-positive value', false);
+
+/// A matcher which matches if the match argument is negative.
+const Matcher isNegative =
+ _OrderingMatcher(0, false, true, false, 'a negative value', false);
+
+/// A matcher which matches if the match argument is zero or positive.
+const Matcher isNonNegative =
+ _OrderingMatcher(0, true, false, true, 'a non-negative value', false);
+
+// TODO(kevmoo) Note that matchers that use _OrderingComparison only use
+// `==` and `<` operators to evaluate the match. Or change the matcher.
+class _OrderingMatcher extends Matcher {
+ /// Expected value.
+ final Object _value;
+
+ /// What to return if actual == expected
+ final bool _equalValue;
+
+ /// What to return if actual < expected
+ final bool _lessThanValue;
+
+ /// What to return if actual > expected
+ final bool _greaterThanValue;
+
+ /// Textual name of the inequality
+ final String _comparisonDescription;
+
+ /// Whether to include the expected value in the description
+ final bool _valueInDescription;
+
+ const _OrderingMatcher(this._value, this._equalValue, this._lessThanValue,
+ this._greaterThanValue, this._comparisonDescription,
+ [bool valueInDescription = true])
+ : _valueInDescription = valueInDescription;
+
+ @override
+ bool matches(Object? item, Map matchState) {
+ if (item == _value) {
+ return _equalValue;
+ } else if ((item as dynamic) < _value) {
+ return _lessThanValue;
+ } else if ((item as dynamic) > _value) {
+ return _greaterThanValue;
+ } else {
+ return false;
+ }
+ }
+
+ @override
+ Description describe(Description description) {
+ if (_valueInDescription) {
+ return description
+ .add(_comparisonDescription)
+ .add(' ')
+ .addDescriptionOf(_value);
+ } else {
+ return description.add(_comparisonDescription);
+ }
+ }
+
+ @override
+ Description describeMismatch(dynamic item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ mismatchDescription.add('is not ');
+ return describe(mismatchDescription);
+ }
+}
diff --git a/pkgs/matcher/lib/src/pretty_print.dart b/pkgs/matcher/lib/src/pretty_print.dart
new file mode 100644
index 0000000..d9eaaec
--- /dev/null
+++ b/pkgs/matcher/lib/src/pretty_print.dart
@@ -0,0 +1,133 @@
+// Copyright (c) 2013, 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 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a pretty-printed representation of [object].
+///
+/// If [maxLineLength] is passed, this will attempt to ensure that each line is
+/// no longer than [maxLineLength] characters long. This isn't guaranteed, since
+/// individual objects may have string representations that are too long, but
+/// most lines will be less than [maxLineLength] long.
+///
+/// If [maxItems] is passed, [Iterable]s and [Map]s will only print their first
+/// [maxItems] members or key/value pairs, respectively.
+String prettyPrint(Object? object, {int? maxLineLength, int? maxItems}) {
+ String prettyPrintImpl(
+ Object? object, int indent, Set<Object?> seen, bool top) {
+ // If the object is a matcher, use its description.
+ if (object is Matcher) {
+ var description = StringDescription();
+ object.describe(description);
+ return '<$description>';
+ }
+
+ // Avoid looping infinitely on recursively-nested data structures.
+ if (seen.contains(object)) return '(recursive)';
+ seen = seen.union({object});
+ String pp(Object? child) => prettyPrintImpl(child, indent + 2, seen, false);
+
+ if (object is Iterable) {
+ // Print the type name for non-List iterables.
+ var type = object is List ? '' : '${_typeName(object)}:';
+
+ // Truncate the list of strings if it's longer than [maxItems].
+ var strings = object.map(pp).toList();
+ if (maxItems != null && strings.length > maxItems) {
+ strings.replaceRange(maxItems - 1, strings.length, ['...']);
+ }
+
+ // If the printed string is short and doesn't contain a newline, print it
+ // as a single line.
+ var singleLine = "$type[${strings.join(', ')}]";
+ if ((maxLineLength == null ||
+ singleLine.length + indent <= maxLineLength) &&
+ !singleLine.contains('\n')) {
+ return singleLine;
+ }
+
+ // Otherwise, print each member on its own line.
+ return '$type[\n${strings.map((string) {
+ return _indent(indent + 2) + string;
+ }).join(',\n')}\n${_indent(indent)}]';
+ } else if (object is Map) {
+ // Convert the contents of the map to string representations.
+ var strings = object.keys.map((key) {
+ return '${pp(key)}: ${pp(object[key])}';
+ }).toList();
+
+ // Truncate the list of strings if it's longer than [maxItems].
+ if (maxItems != null && strings.length > maxItems) {
+ strings.replaceRange(maxItems - 1, strings.length, ['...']);
+ }
+
+ // If the printed string is short and doesn't contain a newline, print it
+ // as a single line.
+ var singleLine = '{${strings.join(", ")}}';
+ if ((maxLineLength == null ||
+ singleLine.length + indent <= maxLineLength) &&
+ !singleLine.contains('\n')) {
+ return singleLine;
+ }
+
+ // Otherwise, print each key/value pair on its own line.
+ return '{\n${strings.map((string) {
+ return _indent(indent + 2) + string;
+ }).join(',\n')}\n${_indent(indent)}}';
+ } else if (object is String) {
+ // Escape strings and print each line on its own line.
+ var value = object
+ .split('\n')
+ .map(_escapeString)
+ .join("\\n'\n${_indent(indent + 2)}'");
+ return "'$value'";
+ } else {
+ var value = object.toString().replaceAll('\n', '${_indent(indent)}\n');
+ var defaultToString = value.startsWith('Instance of ');
+
+ // If this is the top-level call to [prettyPrint], wrap the value on angle
+ // brackets to set it apart visually.
+ if (top) value = '<$value>';
+
+ // Print the type of objects with custom [toString] methods. Primitive
+ // objects and objects that don't implement a custom [toString] don't need
+ // to have their types printed.
+ if (object is num ||
+ object is bool ||
+ object is Function ||
+ object is RegExp ||
+ object is MapEntry ||
+ object is Expando ||
+ object == null ||
+ defaultToString) {
+ return value;
+ } else {
+ return '${_typeName(object)}:$value';
+ }
+ }
+ }
+
+ return prettyPrintImpl(object, 0, <Object?>{}, true);
+}
+
+String _indent(int length) => List.filled(length, ' ').join('');
+
+/// Returns the name of the type of [x] with fallbacks for core types with
+/// private implementations.
+String _typeName(Object x) {
+ if (x is Type) return 'Type';
+ if (x is Uri) return 'Uri';
+ if (x is Set) return 'Set';
+ if (x is BigInt) return 'BigInt';
+ return '${x.runtimeType}';
+}
+
+/// Returns [source] with any control characters replaced by their escape
+/// sequences.
+///
+/// This doesn't add quotes to the string, but it does escape single quote
+/// characters so that single quotes can be applied externally.
+String _escapeString(String source) => escape(source).replaceAll("'", r"\'");
diff --git a/pkgs/matcher/lib/src/string_matchers.dart b/pkgs/matcher/lib/src/string_matchers.dart
new file mode 100644
index 0000000..b819fa5
--- /dev/null
+++ b/pkgs/matcher/lib/src/string_matchers.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2012, 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 'feature_matcher.dart';
+import 'interfaces.dart';
+
+/// Returns a matcher which matches if the match argument is a string and
+/// is equal to [value] when compared case-insensitively.
+Matcher equalsIgnoringCase(String value) => _IsEqualIgnoringCase(value);
+
+class _IsEqualIgnoringCase extends FeatureMatcher<String> {
+ final String _value;
+ final String _matchValue;
+
+ _IsEqualIgnoringCase(String value)
+ : _value = value,
+ _matchValue = value.toLowerCase();
+
+ @override
+ bool typedMatches(String item, Map matchState) =>
+ _matchValue == item.toLowerCase();
+
+ @override
+ Description describe(Description description) =>
+ description.addDescriptionOf(_value).add(' ignoring case');
+}
+
+/// Returns a matcher which matches if the match argument is a string and
+/// is equal to [value], ignoring whitespace.
+///
+/// In this matcher, "ignoring whitespace" means comparing with all runs of
+/// whitespace collapsed to single space characters and leading and trailing
+/// whitespace removed.
+///
+/// For example, the following will all match successfully:
+///
+/// expect("hello world", equalsIgnoringWhitespace("hello world"));
+/// expect(" hello world", equalsIgnoringWhitespace("hello world"));
+/// expect("hello world ", equalsIgnoringWhitespace("hello world"));
+///
+/// The following will not match:
+///
+/// expect("helloworld", equalsIgnoringWhitespace("hello world"));
+/// expect("he llo world", equalsIgnoringWhitespace("hello world"));
+Matcher equalsIgnoringWhitespace(String value) =>
+ _IsEqualIgnoringWhitespace(value);
+
+class _IsEqualIgnoringWhitespace extends FeatureMatcher<String> {
+ final String _matchValue;
+
+ _IsEqualIgnoringWhitespace(String value)
+ : _matchValue = collapseWhitespace(value);
+
+ @override
+ bool typedMatches(String item, Map matchState) =>
+ _matchValue == collapseWhitespace(item);
+
+ @override
+ Description describe(Description description) =>
+ description.addDescriptionOf(_matchValue).add(' ignoring whitespace');
+
+ @override
+ Description describeTypedMismatch(dynamic item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ return mismatchDescription
+ .add('is ')
+ .addDescriptionOf(collapseWhitespace(item))
+ .add(' with whitespace compressed');
+ }
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// starts with [prefixString].
+Matcher startsWith(String prefixString) => _StringStartsWith(prefixString);
+
+class _StringStartsWith extends FeatureMatcher<String> {
+ final String _prefix;
+
+ const _StringStartsWith(this._prefix);
+
+ @override
+ bool typedMatches(String item, Map matchState) => item.startsWith(_prefix);
+
+ @override
+ Description describe(Description description) =>
+ description.add('a string starting with ').addDescriptionOf(_prefix);
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// ends with [suffixString].
+Matcher endsWith(String suffixString) => _StringEndsWith(suffixString);
+
+class _StringEndsWith extends FeatureMatcher<String> {
+ final String _suffix;
+
+ const _StringEndsWith(this._suffix);
+
+ @override
+ bool typedMatches(String item, Map matchState) => item.endsWith(_suffix);
+
+ @override
+ Description describe(Description description) =>
+ description.add('a string ending with ').addDescriptionOf(_suffix);
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// contains a given list of [substrings] in relative order.
+///
+/// For example, `stringContainsInOrder(["a", "e", "i", "o", "u"])` will match
+/// "abcdefghijklmnopqrstuvwxyz".
+
+Matcher stringContainsInOrder(List<String> substrings) =>
+ _StringContainsInOrder(substrings);
+
+class _StringContainsInOrder extends FeatureMatcher<String> {
+ final List<String> _substrings;
+
+ const _StringContainsInOrder(this._substrings);
+
+ @override
+ bool typedMatches(String item, Map matchState) {
+ var fromIndex = 0;
+ for (var s in _substrings) {
+ var index = item.indexOf(s, fromIndex);
+ if (index < 0) return false;
+ fromIndex = index + s.length;
+ }
+ return true;
+ }
+
+ @override
+ Description describe(Description description) => description.addAll(
+ 'a string containing ', ', ', ' in order', _substrings);
+}
+
+/// Returns a matcher that matches if the match argument is a string and
+/// matches the regular expression given by [re].
+///
+/// [re] can be a [RegExp] instance or a [String]; in the latter case it will be
+/// used to create a RegExp instance.
+Matcher matches(Pattern re) => _MatchesRegExp(re);
+
+class _MatchesRegExp extends FeatureMatcher<String> {
+ final RegExp _regexp;
+
+ _MatchesRegExp(Pattern re)
+ : _regexp = (re is String)
+ ? RegExp(re)
+ : (re is RegExp)
+ ? re
+ : throw ArgumentError('matches requires a regexp or string');
+
+ @override
+ bool typedMatches(dynamic item, Map matchState) => _regexp.hasMatch(item);
+
+ @override
+ Description describe(Description description) =>
+ description.add("match '${_regexp.pattern}'");
+}
+
+/// Utility function to collapse whitespace runs to single spaces
+/// and strip leading/trailing whitespace.
+String collapseWhitespace(String string) {
+ var result = StringBuffer();
+ var skipSpace = true;
+ for (var i = 0; i < string.length; i++) {
+ var character = string[i];
+ if (_isWhitespace(character)) {
+ if (!skipSpace) {
+ result.write(' ');
+ skipSpace = true;
+ }
+ } else {
+ result.write(character);
+ skipSpace = false;
+ }
+ }
+ return result.toString().trim();
+}
+
+bool _isWhitespace(String ch) =>
+ ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
diff --git a/pkgs/matcher/lib/src/type_matcher.dart b/pkgs/matcher/lib/src/type_matcher.dart
new file mode 100644
index 0000000..9d32b9f
--- /dev/null
+++ b/pkgs/matcher/lib/src/type_matcher.dart
@@ -0,0 +1,115 @@
+// Copyright (c) 2018, 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 'package:meta/meta.dart';
+
+import 'having_matcher.dart';
+import 'interfaces.dart';
+
+/// Returns a matcher that matches objects with type [T].
+///
+/// ```dart
+/// expect(shouldBeDuration, isA<Duration>());
+/// ```
+///
+/// Expectations can be chained on top of the type using the
+/// [TypeMatcher.having] method to add additional constraints.
+TypeMatcher<T> isA<T>() => TypeMatcher<T>();
+
+/// A [Matcher] subclass that supports validating the [Type] of the target
+/// object.
+///
+/// ```dart
+/// expect(shouldBeDuration, TypeMatcher<Duration>());
+/// ```
+///
+/// If you want to further validate attributes of the specified [Type], use the
+/// [having] function.
+///
+/// ```dart
+/// void shouldThrowRangeError(int value) {
+/// throw RangeError.range(value, 10, 20);
+/// }
+///
+/// expect(
+/// () => shouldThrowRangeError(5),
+/// throwsA(const TypeMatcher<RangeError>()
+/// .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+/// .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+/// ```
+///
+/// Notice that you can chain multiple calls to [having] to verify multiple
+/// aspects of an object.
+///
+/// Note: All of the top-level `isType` matchers exposed by this package are
+/// instances of [TypeMatcher], so you can use the [having] function without
+/// creating your own instance.
+///
+/// ```dart
+/// expect(
+/// () => shouldThrowRangeError(5),
+/// throwsA(isRangeError
+/// .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+/// .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+/// ```
+class TypeMatcher<T> extends Matcher {
+ final String? _name;
+
+ /// Create a matcher matches instances of type [T].
+ ///
+ /// For a fluent API to create TypeMatchers see [isA].
+ const TypeMatcher(
+ [@Deprecated('Provide a type argument to TypeMatcher and omit the name. '
+ 'This argument will be removed in the next release.')
+ String? name])
+ : _name =
+ // ignore: deprecated_member_use_from_same_package
+ name;
+
+ /// Returns a new [TypeMatcher] that validates the existing type as well as
+ /// a specific [feature] of the object with the provided [matcher].
+ ///
+ /// Provides a human-readable [description] of the [feature] to make debugging
+ /// failures easier.
+ ///
+ /// ```dart
+ /// /// Validates that the object is a [RangeError] with a message containing
+ /// /// the string 'details' and `start` and `end` properties that are `null`.
+ /// final _rangeMatcher = isRangeError
+ /// .having((e) => e.message, 'message', contains('details'))
+ /// .having((e) => e.start, 'start', isNull)
+ /// .having((e) => e.end, 'end', isNull);
+ /// ```
+ @useResult
+ TypeMatcher<T> having(
+ Object? Function(T) feature, String description, dynamic matcher) =>
+ HavingMatcher(this, description, feature, matcher);
+
+ @override
+ Description describe(Description description) {
+ var name = _name ?? _stripDynamic(T);
+ return description.add("<Instance of '$name'>");
+ }
+
+ @override
+ bool matches(Object? item, Map matchState) => item is T;
+
+ @override
+ Description describeMismatch(dynamic item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ var name = _name ?? _stripDynamic(T);
+ return mismatchDescription.add("is not an instance of '$name'");
+ }
+}
+
+final _dart2DynamicArgs = RegExp('<dynamic(, dynamic)*>');
+
+/// With this expression `{}.runtimeType.toString`,
+/// Dart 1: "`<Instance of Map>`"
+/// Dart 2: "`<Instance of Map<dynamic, dynamic>>`"
+///
+/// This functions returns the Dart 1 output, when Dart 2 runtime semantics
+/// are enabled.
+String _stripDynamic(Type type) =>
+ type.toString().replaceAll(_dart2DynamicArgs, '');
diff --git a/pkgs/matcher/lib/src/util.dart b/pkgs/matcher/lib/src/util.dart
new file mode 100644
index 0000000..af0ba2c
--- /dev/null
+++ b/pkgs/matcher/lib/src/util.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2014, 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 'core_matchers.dart';
+import 'equals_matcher.dart';
+import 'interfaces.dart';
+
+/// A [Map] between whitespace characters and their escape sequences.
+const _escapeMap = {
+ '\n': r'\n',
+ '\r': r'\r',
+ '\f': r'\f',
+ '\b': r'\b',
+ '\t': r'\t',
+ '\v': r'\v',
+ '\x7F': r'\x7F', // delete
+};
+
+/// A [RegExp] that matches whitespace characters that should be escaped.
+final _escapeRegExp = RegExp(
+ '[\\x00-\\x07\\x0E-\\x1F${_escapeMap.keys.map(_getHexLiteral).join()}]');
+
+/// Useful utility for nesting match states.
+void addStateInfo(Map matchState, Map values) {
+ var innerState = Map.of(matchState);
+ matchState.clear();
+ matchState['state'] = innerState;
+ matchState.addAll(values);
+}
+
+/// Takes an argument and returns an equivalent [Matcher].
+///
+/// If the argument is already a matcher this does nothing,
+/// else if the argument is a function, it generates a predicate
+/// function matcher, else it generates an equals matcher.
+Matcher wrapMatcher(Object? valueOrMatcher) {
+ if (valueOrMatcher is Matcher) {
+ return valueOrMatcher;
+ } else if (valueOrMatcher is bool Function(Object?)) {
+ // already a predicate that can handle anything
+ return predicate(valueOrMatcher);
+ } else if (valueOrMatcher is bool Function(Never)) {
+ // unary predicate, but expects a specific type
+ // so wrap it.
+ // ignore: unnecessary_lambdas
+ return predicate((a) => (valueOrMatcher as dynamic)(a));
+ } else {
+ return equals(valueOrMatcher);
+ }
+}
+
+/// Returns [str] with all whitespace characters represented as their escape
+/// sequences.
+///
+/// Backslash characters are escaped as `\\`
+String escape(String str) {
+ str = str.replaceAll('\\', r'\\');
+ return str.replaceAllMapped(_escapeRegExp, (match) {
+ var mapped = _escapeMap[match[0]];
+ if (mapped != null) return mapped;
+ return _getHexLiteral(match[0]!);
+ });
+}
+
+/// Given single-character string, return the hex-escaped equivalent.
+String _getHexLiteral(String input) {
+ var rune = input.runes.single;
+ return r'\x' + rune.toRadixString(16).toUpperCase().padLeft(2, '0');
+}
diff --git a/pkgs/matcher/mono_pkg.yaml b/pkgs/matcher/mono_pkg.yaml
new file mode 100644
index 0000000..9ef7927
--- /dev/null
+++ b/pkgs/matcher/mono_pkg.yaml
@@ -0,0 +1,17 @@
+# See https://pub.dev/packages/mono_repo
+
+sdk:
+- dev
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+ - group:
+ - analyze
+ sdk: pubspec
+- unit_test:
+ - group:
+ - command: dart test
+ sdk: [dev, pubspec]
diff --git a/pkgs/matcher/pubspec.yaml b/pkgs/matcher/pubspec.yaml
new file mode 100644
index 0000000..237e559
--- /dev/null
+++ b/pkgs/matcher/pubspec.yaml
@@ -0,0 +1,25 @@
+name: matcher
+version: 0.12.18-wip
+description: >-
+ Support for specifying test expectations via an extensible Matcher class.
+ Also includes a number of built-in Matcher implementations for common cases.
+repository: https://github.com/dart-lang/test/tree/master/pkgs/matcher
+
+environment:
+ sdk: ^3.4.0
+
+dependencies:
+ async: ^2.10.0
+ meta: ^1.8.0
+ stack_trace: ^1.10.0
+ term_glyph: ^1.2.0
+ test_api: ">=0.5.0 <0.8.0"
+
+dev_dependencies:
+ fake_async: ^1.3.0
+ lints: ^3.0.0
+ test: ^1.23.0
+
+dependency_overrides:
+ test: 1.25.0
+ test_api: 0.7.3
diff --git a/pkgs/matcher/test/core_matchers_test.dart b/pkgs/matcher/test/core_matchers_test.dart
new file mode 100644
index 0000000..04fc8b3
--- /dev/null
+++ b/pkgs/matcher/test/core_matchers_test.dart
@@ -0,0 +1,247 @@
+// Copyright (c) 2012, 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 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test, group;
+
+import 'test_utils.dart';
+
+void main() {
+ test('isTrue', () {
+ shouldPass(true, isTrue);
+ shouldFail(false, isTrue, 'Expected: true Actual: <false>');
+ });
+
+ test('isFalse', () {
+ shouldPass(false, isFalse);
+ shouldFail(10, isFalse, 'Expected: false Actual: <10>');
+ shouldFail(true, isFalse, 'Expected: false Actual: <true>');
+ });
+
+ test('isNull', () {
+ shouldPass(null, isNull);
+ shouldFail(false, isNull, 'Expected: null Actual: <false>');
+ });
+
+ test('isNotNull', () {
+ shouldPass(false, isNotNull);
+ shouldFail(null, isNotNull, 'Expected: not null Actual: <null>');
+ });
+
+ test('isNaN', () {
+ shouldPass(double.nan, isNaN);
+ shouldFail(3.1, isNaN, 'Expected: NaN Actual: <3.1>');
+ shouldFail('not a num', isNaN, endsWith('not an <Instance of \'num\'>'));
+ });
+
+ test('isNotNaN', () {
+ shouldPass(3.1, isNotNaN);
+ shouldFail(double.nan, isNotNaN, 'Expected: not NaN Actual: <NaN>');
+ shouldFail('not a num', isNotNaN, endsWith('not an <Instance of \'num\'>'));
+ });
+
+ test('same', () {
+ var a = {};
+ var b = {};
+ shouldPass(a, same(a));
+ shouldFail(b, same(a), 'Expected: same instance as {} Actual: {}');
+ });
+
+ test('equals', () {
+ var a = {};
+ var b = {};
+ shouldPass(a, equals(a));
+ shouldPass(a, equals(b));
+ });
+
+ test('equals with null', () {
+ Object? a; // null
+ var b = {};
+ shouldPass(a, equals(a));
+ shouldFail(
+ a, equals(b), 'Expected: {} Actual: <null> Which: expected a map');
+ shouldFail(b, equals(a), 'Expected: <null> Actual: {}');
+ });
+
+ test('equals with a set', () {
+ var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+ var set1 = numbers.toSet();
+ numbers.shuffle();
+ var set2 = numbers.toSet();
+
+ shouldPass(set2, equals(set1));
+ shouldPass(numbers, equals(set1));
+ shouldFail(
+ [1, 2, 3, 4, 5, 6, 7, 8, 9],
+ equals(set1),
+ matches(r'Expected: .*:\[1, 2, 3, 4, 5, 6, 7, 8, 9, 10\]'
+ r' Actual: \[1, 2, 3, 4, 5, 6, 7, 8, 9\]'
+ r' Which: does not contain <10>'));
+ shouldFail(
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+ equals(set1),
+ matches(r'Expected: .*:\[1, 2, 3, 4, 5, 6, 7, 8, 9, 10\]'
+ r' Actual: \[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11\]'
+ r' Which: larger than expected'));
+ });
+
+ test('anything', () {
+ var a = {};
+ shouldPass(0, anything);
+ shouldPass(null, anything);
+ shouldPass(a, anything);
+ shouldFail(a, isNot(anything), 'Expected: not anything Actual: {}');
+ });
+
+ test('returnsNormally', () {
+ shouldPass(doesNotThrow, returnsNormally);
+ shouldFail(
+ doesThrow,
+ returnsNormally,
+ matches(r'Expected: return normally'
+ r' Actual: <Closure.*>'
+ r' Which: threw StateError:<Bad state: X>'));
+ shouldFail('not a function', returnsNormally,
+ contains('not an <Instance of \'Function\'>'));
+ });
+
+ test('hasLength', () {
+ var a = {};
+ var b = [];
+ shouldPass(a, hasLength(0));
+ shouldPass(b, hasLength(0));
+ shouldPass('a', hasLength(1));
+ shouldFail(
+ 0,
+ hasLength(0),
+ 'Expected: an object with length of <0> '
+ 'Actual: <0> '
+ 'Which: has no length property');
+
+ b.add(0);
+ shouldPass(b, hasLength(1));
+ shouldFail(
+ b,
+ hasLength(2),
+ 'Expected: an object with length of <2> '
+ 'Actual: [0] '
+ 'Which: has length of <1>');
+
+ b.add(0);
+ shouldFail(
+ b,
+ hasLength(1),
+ 'Expected: an object with length of <1> '
+ 'Actual: [0, 0] '
+ 'Which: has length of <2>');
+ shouldPass(b, hasLength(2));
+ });
+
+ test('scalar type mismatch', () {
+ shouldFail(
+ 'error',
+ equals(5.1),
+ 'Expected: <5.1> '
+ "Actual: 'error'");
+ });
+
+ test('nested type mismatch', () {
+ shouldFail(
+ ['error'],
+ equals([5.1]),
+ 'Expected: [5.1] '
+ "Actual: ['error'] "
+ "Which: at location [0] is 'error' instead of <5.1>");
+ });
+
+ test('doubly-nested type mismatch', () {
+ shouldFail(
+ [
+ ['error']
+ ],
+ equals([
+ [5.1]
+ ]),
+ 'Expected: [[5.1]] '
+ "Actual: [['error']] "
+ "Which: at location [0][0] is 'error' instead of <5.1>");
+ });
+
+ test('doubly nested inequality', () {
+ var actual1 = [
+ ['foo', 'bar'],
+ ['foo'],
+ 3,
+ []
+ ];
+ var expected1 = [
+ ['foo', 'bar'],
+ ['foo'],
+ 4,
+ []
+ ];
+ var reason1 = "Expected: [['foo', 'bar'], ['foo'], 4, []] "
+ "Actual: [['foo', 'bar'], ['foo'], 3, []] "
+ 'Which: at location [2] is <3> instead of <4>';
+
+ var actual2 = [
+ ['foo', 'barry'],
+ ['foo'],
+ 4,
+ []
+ ];
+ var expected2 = [
+ ['foo', 'bar'],
+ ['foo'],
+ 4,
+ []
+ ];
+ var reason2 = "Expected: [['foo', 'bar'], ['foo'], 4, []] "
+ "Actual: [['foo', 'barry'], ['foo'], 4, []] "
+ "Which: at location [0][1] is 'barry' instead of 'bar'";
+
+ var actual3 = [
+ ['foo', 'bar'],
+ ['foo'],
+ 4,
+ {'foo': 'bar'}
+ ];
+ var expected3 = [
+ ['foo', 'bar'],
+ ['foo'],
+ 4,
+ {'foo': 'barry'}
+ ];
+ var reason3 = "Expected: [['foo', 'bar'], ['foo'], 4, {'foo': 'barry'}] "
+ "Actual: [['foo', 'bar'], ['foo'], 4, {'foo': 'bar'}] "
+ "Which: at location [3]['foo'] is 'bar' instead of 'barry'";
+
+ shouldFail(actual1, equals(expected1), reason1);
+ shouldFail(actual2, equals(expected2), reason2);
+ shouldFail(actual3, equals(expected3), reason3);
+ });
+
+ group('Predicate Matchers', () {
+ test('isInstanceOf', () {
+ shouldFail(0, predicate((x) => x is String, 'an instance of String'),
+ 'Expected: an instance of String Actual: <0>');
+ shouldPass('cow', predicate((x) => x is String, 'an instance of String'));
+
+ shouldFail(0, predicate((bool x) => x, 'bool value is true'),
+ endsWith("not an <Instance of 'bool'>"));
+ });
+ });
+
+ group('wrapMatcher', () {
+ test('wraps a predicate which allows a nullable argument', () {
+ final matcher = wrapMatcher((_) => true);
+ shouldPass(null, matcher);
+ });
+
+ test('wraps a predicate which has a typed argument check', () {
+ final matcher = wrapMatcher((int _) => true);
+ shouldPass(1, matcher);
+ });
+ });
+}
diff --git a/pkgs/matcher/test/custom_matcher_test.dart b/pkgs/matcher/test/custom_matcher_test.dart
new file mode 100644
index 0000000..d0a17c9
--- /dev/null
+++ b/pkgs/matcher/test/custom_matcher_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2012, 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 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test;
+
+import 'test_utils.dart';
+
+class _BadCustomMatcher extends CustomMatcher {
+ _BadCustomMatcher() : super('feature', 'description', {1: 'a'});
+ @override
+ Object? featureValueOf(dynamic actual) => throw Exception('bang');
+}
+
+class _HasPrice extends CustomMatcher {
+ _HasPrice(Object? matcher)
+ : super('Widget with a price that is', 'price', matcher);
+ @override
+ Object? featureValueOf(Object? actual) => (actual as Widget).price;
+}
+
+void main() {
+ test('Feature Matcher', () {
+ var w = Widget();
+ w.price = 10;
+ shouldPass(w, _HasPrice(10));
+ shouldPass(w, _HasPrice(greaterThan(0)));
+ shouldFail(
+ w,
+ _HasPrice(greaterThan(10)),
+ 'Expected: Widget with a price that is a value greater than <10> '
+ "Actual: <Instance of 'Widget'> "
+ 'Which: has price with value <10> which is not '
+ 'a value greater than <10>');
+ });
+
+ test('Custom Matcher Exception', () {
+ shouldFail(
+ 'a',
+ _BadCustomMatcher(),
+ allOf([
+ contains("Expected: feature {1: 'a'} "),
+ contains("Actual: 'a' "),
+ contains("Which: threw 'Exception: bang' "),
+ ]));
+ });
+}
diff --git a/pkgs/matcher/test/escape_test.dart b/pkgs/matcher/test/escape_test.dart
new file mode 100644
index 0000000..c898054
--- /dev/null
+++ b/pkgs/matcher/test/escape_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2015, 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.
+
+// ignore_for_file: missing_whitespace_between_adjacent_strings
+
+import 'package:test/test.dart';
+
+void main() {
+ group('escaping should work with', () {
+ _testEscaping('no escaped chars', 'Hello, world!', 'Hello, world!');
+ _testEscaping('newline', '\n', r'\n');
+ _testEscaping('carriage return', '\r', r'\r');
+ _testEscaping('form feed', '\f', r'\f');
+ _testEscaping('backspace', '\b', r'\b');
+ _testEscaping('tab', '\t', r'\t');
+ _testEscaping('vertical tab', '\v', r'\v');
+ _testEscaping('null byte', '\x00', r'\x00');
+ _testEscaping('ASCII control character', '\x11', r'\x11');
+ _testEscaping('delete', '\x7F', r'\x7F');
+ _testEscaping('escape combos', r'\n', r'\\n');
+ _testEscaping(
+ 'All characters',
+ 'A new line\nA charriage return\rA form feed\fA backspace\b'
+ 'A tab\tA vertical tab\vA slash\\A null byte\x00A control char\x1D'
+ 'A delete\x7F',
+ r'A new line\nA charriage return\rA form feed\fA backspace\b'
+ r'A tab\tA vertical tab\vA slash\\A null byte\x00A control char\x1D'
+ r'A delete\x7F');
+ });
+
+ group('unequal strings remain unequal when escaped', () {
+ _testUnequalStrings('with a newline', '\n', r'\n');
+ _testUnequalStrings('with slash literals', '\\', r'\\');
+ });
+}
+
+/// Creates a [test] with name [name] that verifies [source] escapes to value
+/// [target].
+void _testEscaping(String name, String source, String target) {
+ test(name, () {
+ var escaped = escape(source);
+ expect(escaped == target, isTrue,
+ reason: 'Expected escaped value: $target\n'
+ ' Actual escaped value: $escaped');
+ });
+}
+
+/// Creates a [test] with name [name] that ensures two different [String] values
+/// [s1] and [s2] remain unequal when escaped.
+void _testUnequalStrings(String name, String s1, String s2) {
+ test(name, () {
+ // Explicitly not using the equals matcher
+ expect(s1 != s2, isTrue, reason: 'The source values should be unequal');
+
+ var escapedS1 = escape(s1);
+ var escapedS2 = escape(s2);
+
+ // Explicitly not using the equals matcher
+ expect(escapedS1 != escapedS2, isTrue,
+ reason: 'Unequal strings, when escaped, should remain unequal.');
+ });
+}
diff --git a/pkgs/matcher/test/expect_async_test.dart b/pkgs/matcher/test/expect_async_test.dart
new file mode 100644
index 0000000..7619893
--- /dev/null
+++ b/pkgs/matcher/test/expect_async_test.dart
@@ -0,0 +1,393 @@
+// Copyright (c) 2015, 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.
+
+// ignore_for_file: only_throw_errors
+
+import 'dart:async';
+
+import 'package:fake_async/fake_async.dart';
+import 'package:test/test.dart';
+import 'package:test_api/hooks_testing.dart';
+
+import 'utils_new.dart';
+
+void main() {
+ group('supports a function with this many arguments:', () {
+ test('0', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync0(() {
+ callbackRun = true;
+ })();
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('1', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync1((int arg) {
+ expect(arg, equals(1));
+ callbackRun = true;
+ })(1);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('2', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync2((arg1, arg2) {
+ expect(arg1, equals(1));
+ expect(arg2, equals(2));
+ callbackRun = true;
+ })(1, 2);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('3', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync3((arg1, arg2, arg3) {
+ expect(arg1, equals(1));
+ expect(arg2, equals(2));
+ expect(arg3, equals(3));
+ callbackRun = true;
+ })(1, 2, 3);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('4', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync4((arg1, arg2, arg3, arg4) {
+ expect(arg1, equals(1));
+ expect(arg2, equals(2));
+ expect(arg3, equals(3));
+ expect(arg4, equals(4));
+ callbackRun = true;
+ })(1, 2, 3, 4);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('5', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync5((arg1, arg2, arg3, arg4, arg5) {
+ expect(arg1, equals(1));
+ expect(arg2, equals(2));
+ expect(arg3, equals(3));
+ expect(arg4, equals(4));
+ expect(arg5, equals(5));
+ callbackRun = true;
+ })(1, 2, 3, 4, 5);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('6', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync6((arg1, arg2, arg3, arg4, arg5, arg6) {
+ expect(arg1, equals(1));
+ expect(arg2, equals(2));
+ expect(arg3, equals(3));
+ expect(arg4, equals(4));
+ expect(arg5, equals(5));
+ expect(arg6, equals(6));
+ callbackRun = true;
+ })(1, 2, 3, 4, 5, 6);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+ });
+
+ group('with optional arguments', () {
+ test('allows them to be passed', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync1(([arg = 1]) {
+ expect(arg, equals(2));
+ callbackRun = true;
+ })(2);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('allows them not to be passed', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync1(([arg = 1]) {
+ expect(arg, equals(1));
+ callbackRun = true;
+ })();
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+ });
+
+ group('by default', () {
+ test("won't allow the test to complete until it's called", () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ callback = expectAsync0(() {});
+ });
+
+ await pumpEventQueue();
+ expect(monitor.state, equals(State.running));
+ callback();
+ await monitor.onDone;
+
+ expectTestPassed(monitor);
+ });
+
+ test('may only be called once', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ var callback = expectAsync0(() {});
+ callback();
+ callback();
+ });
+
+ expectTestFailed(
+ monitor, 'Callback called more times than expected (1).');
+ });
+ });
+
+ group('with count', () {
+ test(
+ "won't allow the test to complete until it's called at least that "
+ 'many times', () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ callback = expectAsync0(() {}, count: 3);
+ });
+
+ await pumpEventQueue();
+ expect(monitor.state, equals(State.running));
+ callback();
+
+ await pumpEventQueue();
+ expect(monitor.state, equals(State.running));
+ callback();
+
+ await pumpEventQueue();
+ expect(monitor.state, equals(State.running));
+ callback();
+
+ await monitor.onDone;
+
+ expectTestPassed(monitor);
+ });
+
+ test("will throw an error if it's called more than that many times",
+ () async {
+ var monitor = await TestCaseMonitor.run(() {
+ var callback = expectAsync0(() {}, count: 3);
+ callback();
+ callback();
+ callback();
+ callback();
+ });
+
+ expectTestFailed(
+ monitor, 'Callback called more times than expected (3).');
+ });
+
+ group('0,', () {
+ test("won't block the test's completion", () {
+ expectAsync0(() {}, count: 0);
+ });
+
+ test("will throw an error if it's ever called", () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expectAsync0(() {}, count: 0)();
+ });
+
+ expectTestFailed(
+ monitor, 'Callback called more times than expected (0).');
+ });
+ });
+ });
+
+ group('with max', () {
+ test('will allow the callback to be called that many times', () {
+ var callback = expectAsync0(() {}, max: 3);
+ callback();
+ callback();
+ callback();
+ });
+
+ test('will allow the callback to be called fewer than that many times', () {
+ var callback = expectAsync0(() {}, max: 3);
+ callback();
+ });
+
+ test("will throw an error if it's called more than that many times",
+ () async {
+ var monitor = await TestCaseMonitor.run(() {
+ var callback = expectAsync0(() {}, max: 3);
+ callback();
+ callback();
+ callback();
+ callback();
+ });
+
+ expectTestFailed(
+ monitor, 'Callback called more times than expected (3).');
+ });
+
+ test('-1, will allow the callback to be called any number of times', () {
+ var callback = expectAsync0(() {}, max: -1);
+ for (var i = 0; i < 20; i++) {
+ callback();
+ }
+ });
+ });
+
+ test('will throw an error if max is less than count', () {
+ expect(() => expectAsync0(() {}, max: 1, count: 2), throwsArgumentError);
+ });
+
+ group('expectAsyncUntil()', () {
+ test("won't allow the test to complete until isDone returns true",
+ () async {
+ late TestCaseMonitor monitor;
+ late Future future;
+ monitor = TestCaseMonitor.start(() {
+ var done = false;
+ var callback = expectAsyncUntil0(() {}, () => done);
+
+ future = () async {
+ await pumpEventQueue();
+ expect(monitor.state, equals(State.running));
+ callback();
+ await pumpEventQueue();
+ expect(monitor.state, equals(State.running));
+ done = true;
+ callback();
+ }();
+ });
+ await monitor.onDone;
+
+ expectTestPassed(monitor);
+ // Ensure that the outer test doesn't complete until the inner future
+ // completes.
+ await future;
+ });
+
+ test("doesn't call isDone until after the callback is called", () {
+ var callbackRun = false;
+ expectAsyncUntil0(() => callbackRun = true, () {
+ expect(callbackRun, isTrue);
+ return true;
+ })();
+ });
+ });
+
+ test('allows errors', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(expectAsync0(() => throw 'oh no'), throwsA('oh no'));
+ });
+
+ expectTestPassed(monitor);
+ });
+
+ test('may be called in a non-test zone', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ var callback = expectAsync0(() {});
+ Zone.root.run(callback);
+ });
+ expectTestPassed(monitor);
+ });
+
+ test('may be called in a FakeAsync zone that does not run further', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ FakeAsync().run((_) {
+ var callback = expectAsync0(() {});
+ callback();
+ });
+ });
+ expectTestPassed(monitor);
+ });
+
+ group('old-style expectAsync()', () {
+ test('works with no arguments', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
+ expectAsync(() {
+ callbackRun = true;
+ })();
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('works with dynamic arguments', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
+ expectAsync((arg1, arg2) {
+ callbackRun = true;
+ })(1, 2);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('works with non-nullable arguments', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
+ expectAsync((int arg1, int arg2) {
+ callbackRun = true;
+ })(1, 2);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test('works with 6 arguments', () async {
+ var callbackRun = false;
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package, avoid_dynamic_calls
+ expectAsync((arg1, arg2, arg3, arg4, arg5, arg6) {
+ callbackRun = true;
+ })(1, 2, 3, 4, 5, 6);
+ });
+
+ expectTestPassed(monitor);
+ expect(callbackRun, isTrue);
+ });
+
+ test("doesn't support a function with 7 arguments", () {
+ // ignore: deprecated_member_use_from_same_package
+ expect(() => expectAsync((a, b, c, d, e, f, g) {}), throwsArgumentError);
+ });
+ });
+}
diff --git a/pkgs/matcher/test/expect_test.dart b/pkgs/matcher/test/expect_test.dart
new file mode 100644
index 0000000..e2ef497
--- /dev/null
+++ b/pkgs/matcher/test/expect_test.dart
@@ -0,0 +1,36 @@
+// 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.
+
+import 'package:test/test.dart';
+
+import 'utils_new.dart';
+
+void main() {
+ group('returned Future from expectLater()', () {
+ test('completes immediately for a sync matcher', () {
+ expect(expectLater(true, isTrue), completes);
+ });
+
+ test('contains the expect failure', () {
+ expect(expectLater(Future.value(true), completion(isFalse)),
+ throwsA(isTestFailure(anything)));
+ });
+
+ test('contains an async error', () {
+ expect(expectLater(Future.error('oh no'), completion(isFalse)),
+ throwsA('oh no'));
+ });
+ });
+
+ group('an async matcher that fails synchronously', () {
+ test('throws synchronously', () {
+ expect(() => expect(() {}, throwsA(anything)),
+ throwsA(isTestFailure(anything)));
+ });
+
+ test('can be used with synchronous operators', () {
+ expect(() {}, isNot(throwsA(anything)));
+ });
+ });
+}
diff --git a/pkgs/matcher/test/having_test.dart b/pkgs/matcher/test/having_test.dart
new file mode 100644
index 0000000..ddada77
--- /dev/null
+++ b/pkgs/matcher/test/having_test.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2018, 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.
+
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test, expect, throwsA, group;
+
+import 'test_utils.dart';
+
+void main() {
+ test('success', () {
+ shouldPass(RangeError('details'), _rangeMatcher);
+ });
+
+ test('failure', () {
+ shouldFail(
+ CustomRangeError.range(-1, 1, 10),
+ _rangeMatcher,
+ "Expected: <Instance of 'RangeError'> with "
+ "`message`: contains 'details' and `start`: null and `end`: null "
+ 'Actual: CustomRangeError:<RangeError: Invalid value: details> '
+ "Which: has `message` with value 'Invalid value' "
+ "which does not contain 'details'",
+ );
+ });
+
+ // This code is used in the [TypeMatcher] doc comments.
+ test('integration and example', () {
+ void shouldThrowRangeError(int value) {
+ throw RangeError.range(value, 10, 20);
+ }
+
+ expect(
+ () => shouldThrowRangeError(5),
+ throwsA(const TypeMatcher<RangeError>()
+ .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+ .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+
+ expect(
+ () => shouldThrowRangeError(5),
+ throwsA(isRangeError
+ .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+ .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+ });
+
+ test('having inside deep matcher', () {
+ shouldFail(
+ [RangeError.range(-1, 1, 10)],
+ equals([_rangeMatcher]),
+ anyOf([
+ equalsIgnoringWhitespace(
+ "Expected: [ <<Instance of 'RangeError'> with "
+ "`message`: contains 'details' and `start`: null and `end`: null> ] "
+ 'Actual: [RangeError:RangeError: '
+ 'Invalid value: Not in inclusive range 1..10: -1] '
+ 'Which: at location [0] is RangeError:<RangeError: '
+ 'Invalid value: Not in inclusive range 1..10: -1> '
+ "which has `message` with value 'Invalid value' "
+ "which does not contain 'details'"),
+ equalsIgnoringWhitespace(// Older SDKs
+ "Expected: [ <<Instance of 'RangeError'> with "
+ "`message`: contains 'details' and `start`: null and `end`: null> ] "
+ 'Actual: [RangeError:RangeError: '
+ 'Invalid value: Not in range 1..10, inclusive: -1] '
+ 'Which: at location [0] is RangeError:<RangeError: '
+ 'Invalid value: Not in range 1..10, inclusive: -1> '
+ "which has `message` with value 'Invalid value' "
+ "which does not contain 'details'")
+ ]));
+ });
+
+ group('CustomMatcher copy', () {
+ test('Feature Matcher', () {
+ var w = Widget();
+ w.price = 10;
+ shouldPass(w, _hasPrice(10));
+ shouldPass(w, _hasPrice(greaterThan(0)));
+ shouldFail(
+ w,
+ _hasPrice(greaterThan(10)),
+ "Expected: <Instance of 'Widget'> with `price`: a value greater than <10> "
+ "Actual: <Instance of 'Widget'> "
+ 'Which: has `price` with value <10> which is not '
+ 'a value greater than <10>');
+ });
+
+ test('Custom Matcher Exception', () {
+ shouldFail(
+ 'a',
+ _badCustomMatcher(),
+ allOf([
+ contains(
+ "Expected: <Instance of 'Widget'> with `feature`: {1: 'a'} "),
+ contains("Actual: 'a'"),
+ ]));
+ shouldFail(
+ Widget(),
+ _badCustomMatcher(),
+ allOf([
+ contains(
+ "Expected: <Instance of 'Widget'> with `feature`: {1: 'a'} "),
+ contains("Actual: <Instance of 'Widget'> "),
+ contains("Which: threw 'Exception: bang' "),
+ ]));
+ });
+ });
+}
+
+final _rangeMatcher = isRangeError
+ .having((e) => e.message, 'message', contains('details'))
+ .having((e) => e.start, 'start', isNull)
+ .having((e) => e.end, 'end', isNull);
+
+Matcher _hasPrice(Object matcher) =>
+ const TypeMatcher<Widget>().having((e) => e.price, 'price', matcher);
+
+Matcher _badCustomMatcher() => const TypeMatcher<Widget>()
+ .having((e) => throw Exception('bang'), 'feature', {1: 'a'});
+
+class CustomRangeError extends RangeError {
+ CustomRangeError.range(
+ super.invalidValue, int super.minValue, int super.maxValue)
+ : super.range();
+
+ @override
+ String toString() => 'RangeError: Invalid value: details';
+}
diff --git a/pkgs/matcher/test/iterable_matchers_test.dart b/pkgs/matcher/test/iterable_matchers_test.dart
new file mode 100644
index 0000000..7607d18
--- /dev/null
+++ b/pkgs/matcher/test/iterable_matchers_test.dart
@@ -0,0 +1,395 @@
+// Copyright (c) 2012, 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 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ test('isEmpty', () {
+ shouldPass([], isEmpty);
+ shouldFail([1], isEmpty, 'Expected: empty Actual: [1]');
+ });
+
+ test('isNotEmpty', () {
+ shouldFail([], isNotEmpty, 'Expected: non-empty Actual: []');
+ shouldPass([1], isNotEmpty);
+ });
+
+ test('contains', () {
+ var d = [1, 2];
+ shouldPass(d, contains(1));
+ shouldFail(
+ d,
+ contains(0),
+ 'Expected: contains <0> '
+ 'Actual: [1, 2] '
+ 'Which: does not contain <0>');
+
+ shouldFail(
+ 'String',
+ contains(42),
+ "Expected: contains <42> Actual: 'String' "
+ 'Which: does not contain <42>');
+ });
+
+ test('equals with matcher element', () {
+ var d = ['foo', 'bar'];
+ shouldPass(d, equals(['foo', startsWith('ba')]));
+ shouldFail(
+ d,
+ equals(['foo', endsWith('ba')]),
+ "Expected: ['foo', <a string ending with 'ba'>] "
+ "Actual: ['foo', 'bar'] "
+ "Which: at location [1] is 'bar' which "
+ "does not match a string ending with 'ba'");
+ });
+
+ test('isIn', () {
+ // Iterable
+ shouldPass(1, isIn([1, 2]));
+ shouldFail(0, isIn([1, 2]), 'Expected: is in [1, 2] Actual: <0>');
+
+ // Map
+ shouldPass(1, isIn({1: null}));
+ shouldFail(0, isIn({1: null}), 'Expected: is in {1: null} Actual: <0>');
+
+ // String
+ shouldPass('42', isIn('1421'));
+ shouldFail('42', isIn('41'), "Expected: is in '41' Actual: '42'");
+ shouldFail(
+ 0, isIn('a string'), endsWith('not an <Instance of \'Pattern\'>'));
+
+ // Invalid arg
+ expect(() => isIn(42), throwsArgumentError);
+ });
+
+ test('everyElement', () {
+ var d = [1, 2];
+ var e = [1, 1, 1];
+ shouldFail(
+ d,
+ everyElement(1),
+ 'Expected: every element(<1>) '
+ 'Actual: [1, 2] '
+ "Which: has value <2> which doesn't match <1> at index 1");
+ shouldPass(e, everyElement(1));
+ shouldFail('not iterable', everyElement(1),
+ endsWith('not an <Instance of \'Iterable\'>'));
+ });
+
+ test('nested everyElement', () {
+ var d = [
+ ['foo', 'bar'],
+ ['foo'],
+ []
+ ];
+ var e = [
+ ['foo', 'bar'],
+ ['foo'],
+ 3,
+ []
+ ];
+ shouldPass(d, everyElement(anyOf(isEmpty, contains('foo'))));
+ shouldFail(
+ d,
+ everyElement(everyElement(equals('foo'))),
+ "Expected: every element(every element('foo')) "
+ "Actual: [['foo', 'bar'], ['foo'], []] "
+ "Which: has value ['foo', 'bar'] which has value 'bar' "
+ 'which is different. Expected: foo Actual: bar ^ '
+ 'Differ at offset 0 at index 1 at index 0');
+ shouldFail(
+ d,
+ everyElement(allOf(hasLength(greaterThan(0)), contains('foo'))),
+ 'Expected: every element((an object with length of a value '
+ "greater than <0> and contains 'foo')) "
+ "Actual: [['foo', 'bar'], ['foo'], []] "
+ 'Which: has value [] which has length of <0> at index 2');
+ shouldFail(
+ d,
+ everyElement(allOf(contains('foo'), hasLength(greaterThan(0)))),
+ "Expected: every element((contains 'foo' and "
+ 'an object with length of a value greater than <0>)) '
+ "Actual: [['foo', 'bar'], ['foo'], []] "
+ "Which: has value [] which does not contain 'foo' at index 2");
+ shouldFail(
+ e,
+ everyElement(allOf(contains('foo'), hasLength(greaterThan(0)))),
+ "Expected: every element((contains 'foo' and an object with "
+ 'length of a value greater than <0>)) '
+ "Actual: [['foo', 'bar'], ['foo'], 3, []] "
+ 'Which: has value <3> which is not a string, map or iterable '
+ 'at index 2');
+ });
+
+ test('anyElement', () {
+ var d = [1, 2];
+ var e = [1, 1, 1];
+ shouldPass(d, anyElement(2));
+ shouldFail(
+ e, anyElement(2), 'Expected: some element <2> Actual: [1, 1, 1]');
+ shouldFail('not an iterable', anyElement(2),
+ endsWith('not an <Instance of \'Iterable\'>'));
+ });
+
+ test('orderedEquals', () {
+ shouldPass([null], orderedEquals([null]));
+ var d = [1, 2];
+ shouldPass(d, orderedEquals([1, 2]));
+ shouldFail(
+ d,
+ orderedEquals([2, 1]),
+ 'Expected: equals [2, 1] ordered '
+ 'Actual: [1, 2] '
+ 'Which: at location [0] is <1> instead of <2>');
+ shouldFail('not an iterable', orderedEquals([1]),
+ endsWith('not an <Instance of \'Iterable\'>'));
+ });
+
+ test('unorderedEquals', () {
+ var d = [1, 2];
+ shouldPass(d, unorderedEquals([2, 1]));
+ shouldFail(
+ d,
+ unorderedEquals([1]),
+ 'Expected: equals [1] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has too many elements (2 > 1)');
+ shouldFail(
+ d,
+ unorderedEquals([3, 2, 1]),
+ 'Expected: equals [3, 2, 1] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has too few elements (2 < 3)');
+ shouldFail(
+ d,
+ unorderedEquals([3, 1]),
+ 'Expected: equals [3, 1] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has no match for <3> at index 0');
+ shouldFail(
+ d,
+ unorderedEquals([3, 4]),
+ 'Expected: equals [3, 4] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has no match for <3> at index 0'
+ ' along with 1 other unmatched');
+ shouldFail('not an iterable', unorderedEquals([1]),
+ endsWith('not an <Instance of \'Iterable\'>'));
+ });
+
+ test('unorderedMatches', () {
+ var d = [1, 2];
+ shouldPass(d, unorderedMatches([2, 1]));
+ shouldPass(d, unorderedMatches([greaterThan(1), greaterThan(0)]));
+ shouldPass(d, unorderedMatches([greaterThan(0), greaterThan(1)]));
+ shouldPass([2, 1], unorderedMatches([greaterThan(1), greaterThan(0)]));
+
+ shouldPass([2, 1], unorderedMatches([greaterThan(0), greaterThan(1)]));
+ // Excersize the case where pairings should get "bumped" multiple times
+ shouldPass(
+ [0, 1, 2, 3, 5, 6],
+ unorderedMatches([
+ greaterThan(1), // 6
+ equals(2), // 2
+ allOf([lessThan(3), isNot(0)]), // 1
+ equals(0), // 0
+ predicate((int v) => v % 2 == 1), // 3
+ equals(5), // 5
+ ]));
+ shouldFail(
+ d,
+ unorderedMatches([greaterThan(0)]),
+ 'Expected: matches [a value greater than <0>] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has too many elements (2 > 1)');
+ shouldFail(
+ d,
+ unorderedMatches([3, 2, 1]),
+ 'Expected: matches [<3>, <2>, <1>] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has too few elements (2 < 3)');
+ shouldFail(
+ d,
+ unorderedMatches([3, 1]),
+ 'Expected: matches [<3>, <1>] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has no match for <3> at index 0');
+ shouldFail(
+ d,
+ unorderedMatches([greaterThan(3), greaterThan(0)]),
+ 'Expected: matches [a value greater than <3>, a value greater than '
+ '<0>] unordered '
+ 'Actual: [1, 2] '
+ 'Which: has no match for a value greater than <3> at index 0');
+ shouldFail('not an iterable', unorderedMatches([greaterThan(1)]),
+ endsWith('not an <Instance of \'Iterable\'>'));
+ });
+
+ test('containsAll', () {
+ var d = [0, 1, 2];
+ shouldPass(d, containsAll([1, 2]));
+ shouldPass(d, containsAll([2, 1]));
+ shouldPass(d, containsAll([greaterThan(0), greaterThan(1)]));
+ shouldPass([2, 1], containsAll([greaterThan(0), greaterThan(1)]));
+ shouldFail(
+ d,
+ containsAll([1, 2, 3]),
+ 'Expected: contains all of [1, 2, 3] '
+ 'Actual: [0, 1, 2] '
+ 'Which: has no match for <3> at index 2');
+ shouldFail(
+ 1,
+ containsAll([1]),
+ 'Expected: contains all of [1] '
+ 'Actual: <1> '
+ "Which: not an <Instance of 'Iterable'>");
+ shouldFail(
+ [-1, 2],
+ containsAll([greaterThan(0), greaterThan(1)]),
+ 'Expected: contains all of [<a value greater than <0>>, '
+ '<a value greater than <1>>] '
+ 'Actual: [-1, 2] '
+ 'Which: has no match for a value greater than <1> at index 1');
+ shouldFail('not an iterable', containsAll([1, 2, 3]),
+ endsWith('not an <Instance of \'Iterable\'>'));
+ });
+
+ test('containsAllInOrder', () {
+ var d = [0, 1, 0, 2];
+ shouldPass(d, containsAllInOrder([1, 2]));
+ shouldPass(d, containsAllInOrder([greaterThan(0), greaterThan(1)]));
+ shouldFail(
+ d,
+ containsAllInOrder([2, 1]),
+ 'Expected: contains in order([2, 1]) '
+ 'Actual: [0, 1, 0, 2] '
+ 'Which: did not find a value matching <1> following expected prior '
+ 'values');
+ shouldFail(
+ d,
+ containsAllInOrder([greaterThan(1), greaterThan(0)]),
+ 'Expected: contains in order([<a value greater than <1>>, '
+ '<a value greater than <0>>]) '
+ 'Actual: [0, 1, 0, 2] '
+ 'Which: did not find a value matching a value greater than <0> '
+ 'following expected prior values');
+ shouldFail(
+ d,
+ containsAllInOrder([1, 2, 3]),
+ 'Expected: contains in order([1, 2, 3]) '
+ 'Actual: [0, 1, 0, 2] '
+ 'Which: did not find a value matching <3> following expected prior '
+ 'values');
+ shouldFail(
+ 1,
+ containsAllInOrder([1]),
+ 'Expected: contains in order([1]) '
+ 'Actual: <1> '
+ "Which: not an <Instance of 'Iterable'>");
+ });
+
+ test('containsOnce', () {
+ shouldPass([1, 2, 3, 4], containsOnce(2));
+ shouldPass([1, 2, 11, 3], containsOnce(greaterThan(10)));
+ shouldFail(
+ [1, 2, 3, 4],
+ containsOnce(10),
+ 'Expected: contains once(<10>) '
+ 'Actual: [1, 2, 3, 4] '
+ 'Which: did not find a value matching <10>');
+ shouldFail(
+ [1, 2, 3, 4],
+ containsOnce(greaterThan(10)),
+ 'Expected: contains once(a value greater than <10>) '
+ 'Actual: [1, 2, 3, 4] '
+ 'Which: did not find a value matching a value greater than <10>');
+ shouldFail(
+ [1, 2, 1, 2],
+ containsOnce(2),
+ 'Expected: contains once(<2>) '
+ 'Actual: [1, 2, 1, 2] '
+ 'Which: expected only one value matching <2> '
+ 'but found multiple: <2>, <2>');
+ shouldFail(
+ [1, 2, 10, 20],
+ containsOnce(greaterThan(5)),
+ 'Expected: contains once(a value greater than <5>) '
+ 'Actual: [1, 2, 10, 20] '
+ 'Which: expected only one value matching a value greater than <5> '
+ 'but found multiple: <10>, <20>');
+ });
+
+ test('pairwise compare', () {
+ var c = [1, 2];
+ var d = [1, 2, 3];
+ var e = [1, 4, 9];
+ shouldFail(
+ 'x',
+ pairwiseCompare(e, (int e, int a) => a <= e, 'less than or equal'),
+ 'Expected: pairwise less than or equal [1, 4, 9] '
+ "Actual: 'x' "
+ "Which: not an <Instance of 'Iterable'>");
+ shouldFail(
+ c,
+ pairwiseCompare(e, (int e, int a) => a <= e, 'less than or equal'),
+ 'Expected: pairwise less than or equal [1, 4, 9] '
+ 'Actual: [1, 2] '
+ 'Which: has length 2 instead of 3');
+ shouldPass(
+ d, pairwiseCompare(e, (int e, int a) => a <= e, 'less than or equal'));
+ shouldFail(
+ d,
+ pairwiseCompare(e, (int e, int a) => a < e, 'less than'),
+ 'Expected: pairwise less than [1, 4, 9] '
+ 'Actual: [1, 2, 3] '
+ 'Which: has <1> which is not less than <1> at index 0');
+ shouldPass(
+ d, pairwiseCompare(e, (int e, int a) => a * a == e, 'square root of'));
+ shouldFail(
+ d,
+ pairwiseCompare(e, (int e, int a) => a + a == e, 'double'),
+ 'Expected: pairwise double [1, 4, 9] '
+ 'Actual: [1, 2, 3] '
+ 'Which: has <1> which is not double <1> at index 0');
+ shouldFail(
+ 'not an iterable',
+ pairwiseCompare(e, (int e, int a) => a + a == e, 'double'),
+ endsWith('not an <Instance of \'Iterable\'>'));
+ });
+
+ test('isEmpty', () {
+ var d = SimpleIterable(0);
+ var e = SimpleIterable(1);
+ shouldPass(d, isEmpty);
+ shouldFail(
+ e,
+ isEmpty,
+ 'Expected: empty '
+ 'Actual: SimpleIterable:[1]');
+ });
+
+ test('isNotEmpty', () {
+ var d = SimpleIterable(0);
+ var e = SimpleIterable(1);
+ shouldPass(e, isNotEmpty);
+ shouldFail(
+ d,
+ isNotEmpty,
+ 'Expected: non-empty '
+ 'Actual: SimpleIterable:[]');
+ });
+
+ test('contains', () {
+ var d = SimpleIterable(3);
+ shouldPass(d, contains(2));
+ shouldFail(
+ d,
+ contains(5),
+ 'Expected: contains <5> '
+ 'Actual: SimpleIterable:[3, 2, 1] '
+ 'Which: does not contain <5>');
+ });
+}
diff --git a/pkgs/matcher/test/map_matchers_test.dart b/pkgs/matcher/test/map_matchers_test.dart
new file mode 100644
index 0000000..4c699ab
--- /dev/null
+++ b/pkgs/matcher/test/map_matchers_test.dart
@@ -0,0 +1,100 @@
+import 'package:matcher/matcher.dart'
+ show contains, containsValue, containsPair;
+import 'package:test/test.dart' show test;
+
+import 'test_utils.dart';
+
+void main() {
+ test('contains', () {
+ shouldPass({'a': 1}, contains('a'));
+ shouldPass({null: 1}, contains(null));
+ shouldFail(
+ {'a': 1},
+ contains(2),
+ 'Expected: contains <2> '
+ 'Actual: {\'a\': 1} '
+ 'Which: does not contain <2>',
+ );
+ shouldFail(
+ {'a': 1},
+ contains(null),
+ 'Expected: contains <null> '
+ 'Actual: {\'a\': 1} '
+ 'Which: does not contain <null>',
+ );
+ });
+
+ test('containsValue', () {
+ shouldPass({'a': 1, 'null': null}, containsValue(1));
+ shouldPass({'a': 1, 'null': null}, containsValue(null));
+ shouldFail(
+ {'a': 1, 'null': null},
+ containsValue(2),
+ 'Expected: contains value <2> '
+ "Actual: {'a': 1, 'null': null}",
+ );
+ });
+
+ test('containsPair', () {
+ shouldPass({'a': 1, 'null': null}, containsPair('a', 1));
+ shouldPass({'a': 1, 'null': null}, containsPair('null', null));
+ shouldPass({null: null}, containsPair(null, null));
+ shouldFail(
+ {'a': 1, 'null': null},
+ containsPair('a', 2),
+ "Expected: contains pair 'a' => <2> "
+ "Actual: {'a': 1, 'null': null} "
+ "Which: contains key 'a' but with value is <1>",
+ );
+ shouldFail(
+ {'a': 1, 'null': null},
+ containsPair('b', 1),
+ "Expected: contains pair 'b' => <1> "
+ "Actual: {'a': 1, 'null': null} "
+ "Which: doesn't contain key 'b'",
+ );
+ shouldFail(
+ {'a': 1, 'null': null},
+ containsPair('null', 2),
+ "Expected: contains pair 'null' => <2> "
+ "Actual: {'a': 1, 'null': null} "
+ "Which: contains key 'null' but with value is <null>",
+ );
+ shouldFail(
+ {'a': 1, 'null': null},
+ containsPair('2', null),
+ "Expected: contains pair '2' => <null> "
+ "Actual: {'a': 1, 'null': null} "
+ "Which: doesn't contain key '2'",
+ );
+ shouldFail(
+ {'a': 1, 'null': null},
+ containsPair('2', 'b'),
+ "Expected: contains pair '2' => 'b' "
+ "Actual: {'a': 1, 'null': null} "
+ "Which: doesn't contain key '2'",
+ );
+ shouldFail(
+ {null: null},
+ containsPair('not null', null),
+ "Expected: contains pair 'not null' => <null> "
+ 'Actual: {null: null} '
+ "Which: doesn't contain key 'not null'",
+ );
+ shouldFail(
+ {null: null},
+ containsPair(null, 'not null'),
+ 'Expected: contains pair <null> => \'not null\' '
+ 'Actual: {null: null} '
+ 'Which: contains key <null> but with value not an '
+ '<Instance of \'String\'>',
+ );
+ shouldFail(
+ {null: null},
+ containsPair('not null', 'not null'),
+ 'Expected: contains pair \'not null\' => \'not null\' '
+ 'Actual: {null: null} '
+ 'Which: doesn\'t contain key \'not null\' ',
+ );
+ });
+}
diff --git a/pkgs/matcher/test/matcher/completion_test.dart b/pkgs/matcher/test/matcher/completion_test.dart
new file mode 100644
index 0000000..9259cd1
--- /dev/null
+++ b/pkgs/matcher/test/matcher/completion_test.dart
@@ -0,0 +1,192 @@
+// Copyright (c) 2015, 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 'package:test/test.dart';
+import 'package:test_api/hooks_testing.dart';
+
+import '../utils_new.dart';
+
+void main() {
+ group('[doesNotComplete]', () {
+ test('fails when provided a non future', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(10, doesNotComplete);
+ });
+
+ expectTestFailed(monitor, contains('10 is not a Future'));
+ });
+
+ test('succeeds when a future does not complete', () {
+ var completer = Completer();
+ expect(completer.future, doesNotComplete);
+ });
+
+ test('fails when a future does complete', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ var completer = Completer();
+ completer.complete(null);
+ expect(completer.future, doesNotComplete);
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Future was not expected to complete but completed with a value of'
+ ' null');
+ });
+
+ test('fails when a future completes after the expect', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ var completer = Completer();
+ expect(completer.future, doesNotComplete);
+ completer.complete(null);
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Future was not expected to complete but completed with a value of'
+ ' null');
+ });
+
+ test('fails when a future eventually completes', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ var completer = Completer();
+ expect(completer.future, doesNotComplete);
+ Future(() async {
+ await pumpEventQueue(times: 10);
+ }).then(completer.complete);
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Future was not expected to complete but completed with a value of'
+ ' null');
+ });
+ });
+ group('[completes]', () {
+ test('blocks the test until the Future completes', () async {
+ final completer = Completer<void>();
+ final monitor = TestCaseMonitor.start(() {
+ expect(completer.future, completes);
+ });
+ await pumpEventQueue();
+ expect(monitor.state, State.running);
+ completer.complete();
+ await monitor.onDone;
+ expectTestPassed(monitor);
+ });
+
+ test('with an error', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.error('X'), completes);
+ });
+
+ expect(monitor.state, equals(State.failed));
+ expect(monitor.errors, [isAsyncError(equals('X'))]);
+ });
+
+ test('with a failure', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.error(TestFailure('oh no')), completes);
+ });
+
+ expectTestFailed(monitor, 'oh no');
+ });
+
+ test('with a non-future', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(10, completes);
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Expected: completes successfully\n'
+ ' Actual: <10>\n'
+ ' Which: was not a Future\n');
+ });
+
+ test('with a successful future', () {
+ expect(Future.value('1'), completes);
+ });
+ });
+
+ group('[completion]', () {
+ test('blocks the test until the Future completes', () async {
+ final completer = Completer<Object?>();
+ final monitor = TestCaseMonitor.start(() {
+ expect(completer.future, completion(isNull));
+ });
+ await pumpEventQueue();
+ expect(monitor.state, State.running);
+ completer.complete(null);
+ await monitor.onDone;
+ expectTestPassed(monitor);
+ });
+
+ test('with an error', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.error('X'), completion(isNull));
+ });
+
+ expect(monitor.state, equals(State.failed));
+ expect(monitor.errors, [isAsyncError(equals('X'))]);
+ });
+
+ test('with a failure', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.error(TestFailure('oh no')), completion(isNull));
+ });
+
+ expectTestFailed(monitor, 'oh no');
+ });
+
+ test('with a non-future', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(10, completion(equals(10)));
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Expected: completes to a value that <10>\n'
+ ' Actual: <10>\n'
+ ' Which: was not a Future\n');
+ });
+
+ test('with an incorrect value', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.value('a'), completion(equals('b')));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: completes to a value that 'b'\n"
+ ' Actual: <'),
+ endsWith('>\n'
+ " Which: emitted 'a'\n"
+ ' which is different.\n'
+ ' Expected: b\n'
+ ' Actual: a\n'
+ ' ^\n'
+ ' Differ at offset 0\n')
+ ]));
+ });
+
+ test("blocks expectLater's Future", () async {
+ var completer = Completer();
+ var fired = false;
+ unawaited(expectLater(completer.future, completion(equals(1))).then((_) {
+ fired = true;
+ }));
+
+ await pumpEventQueue();
+ expect(fired, isFalse);
+
+ completer.complete(1);
+ await pumpEventQueue();
+ expect(fired, isTrue);
+ });
+ });
+}
diff --git a/pkgs/matcher/test/matcher/prints_test.dart b/pkgs/matcher/test/matcher/prints_test.dart
new file mode 100644
index 0000000..cbdb12a
--- /dev/null
+++ b/pkgs/matcher/test/matcher/prints_test.dart
@@ -0,0 +1,208 @@
+// Copyright (c) 2014, 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 'package:test/test.dart';
+import 'package:test_api/hooks_testing.dart';
+
+import '../utils_new.dart';
+
+void main() {
+ group('synchronous', () {
+ test('passes with an expected print', () {
+ expect(() => print('Hello, world!'), prints('Hello, world!\n'));
+ });
+
+ test('combines multiple prints', () {
+ expect(() {
+ print('Hello');
+ print('World!');
+ }, prints('Hello\nWorld!\n'));
+ });
+
+ test('works with a Matcher', () {
+ expect(() => print('Hello, world!'), prints(contains('Hello')));
+ });
+
+ test('describes a failure nicely', () async {
+ void local() => print('Hello, world!');
+ var monitor = await TestCaseMonitor.run(() {
+ expect(local, prints('Goodbye, world!\n'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: prints 'Goodbye, world!\\n'\n"
+ " ''\n"
+ ' Actual: <'),
+ endsWith('>\n'
+ " Which: printed 'Hello, world!\\n'\n"
+ " ''\n"
+ ' which is different.\n'
+ ' Expected: Goodbye, w ...\n'
+ ' Actual: Hello, wor ...\n'
+ ' ^\n'
+ ' Differ at offset 0\n')
+ ]));
+ });
+
+ test('describes a failure with a non-descriptive Matcher nicely', () async {
+ void local() => print('Hello, world!');
+ var monitor = await TestCaseMonitor.run(() {
+ expect(local, prints(contains('Goodbye')));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: prints contains 'Goodbye'\n"
+ ' Actual: <'),
+ endsWith('>\n'
+ " Which: printed 'Hello, world!\\n'\n"
+ " ''\n"
+ ' which does not contain \'Goodbye\'\n')
+ ]));
+ });
+
+ test('describes a failure with no text nicely', () async {
+ void local() {}
+ var monitor = await TestCaseMonitor.run(() {
+ expect(local, prints(contains('Goodbye')));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: prints contains 'Goodbye'\n"
+ ' Actual: <'),
+ endsWith('>\n'
+ ' Which: printed nothing\n'
+ ' which does not contain \'Goodbye\'\n')
+ ]));
+ });
+
+ test('with a non-function', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(10, prints(contains('Goodbye')));
+ });
+
+ expectTestFailed(
+ monitor,
+ "Expected: prints contains 'Goodbye'\n"
+ ' Actual: <10>\n'
+ ' Which: was not a unary Function\n');
+ });
+ });
+
+ group('asynchronous', () {
+ test('passes with an expected print', () {
+ expect(() => Future(() => print('Hello, world!')),
+ prints('Hello, world!\n'));
+ });
+
+ test('combines multiple prints', () {
+ expect(
+ () => Future(() {
+ print('Hello');
+ print('World!');
+ }),
+ prints('Hello\nWorld!\n'));
+ });
+
+ test('works with a Matcher', () {
+ expect(() => Future(() => print('Hello, world!')),
+ prints(contains('Hello')));
+ });
+
+ test('describes a failure nicely', () async {
+ void local() => Future(() => print('Hello, world!'));
+ var monitor = await TestCaseMonitor.run(() {
+ expect(local, prints('Goodbye, world!\n'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: prints 'Goodbye, world!\\n'\n"
+ " ''\n"
+ ' Actual: <'),
+ contains('>\n'
+ " Which: printed 'Hello, world!\\n'\n"
+ " ''\n"
+ ' which is different.\n'
+ ' Expected: Goodbye, w ...\n'
+ ' Actual: Hello, wor ...\n'
+ ' ^\n'
+ ' Differ at offset 0')
+ ]));
+ });
+
+ test('describes a failure with a non-descriptive Matcher nicely', () async {
+ void local() => Future(() => print('Hello, world!'));
+ var monitor = await TestCaseMonitor.run(() {
+ expect(local, prints(contains('Goodbye')));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: prints contains 'Goodbye'\n"
+ ' Actual: <'),
+ contains('>\n'
+ " Which: printed 'Hello, world!\\n'\n"
+ " ''")
+ ]));
+ });
+
+ test('describes a failure with no text nicely', () async {
+ void local() => Future.value();
+ var monitor = await TestCaseMonitor.run(() {
+ expect(local, prints(contains('Goodbye')));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: prints contains 'Goodbye'\n"
+ ' Actual: <'),
+ contains('>\n'
+ ' Which: printed nothing')
+ ]));
+ });
+
+ test("won't let the test end until the Future completes", () async {
+ final completer = Completer<void>();
+ final monitor = TestCaseMonitor.start(() {
+ expect(() => completer.future, prints(isEmpty));
+ });
+ await pumpEventQueue();
+ expect(monitor.state, State.running);
+ completer.complete();
+ await monitor.onDone;
+ expectTestPassed(monitor);
+ });
+
+ test("blocks expectLater's Future", () async {
+ var completer = Completer();
+ var fired = false;
+
+ unawaited(expectLater(() {
+ scheduleMicrotask(() => print('hello!'));
+ return completer.future;
+ }, prints('hello!\n'))
+ .then((_) {
+ fired = true;
+ }));
+
+ await pumpEventQueue();
+ expect(fired, isFalse);
+
+ completer.complete();
+ await pumpEventQueue();
+ expect(fired, isTrue);
+ });
+ });
+}
diff --git a/pkgs/matcher/test/matcher/throws_test.dart b/pkgs/matcher/test/matcher/throws_test.dart
new file mode 100644
index 0000000..25b93a9
--- /dev/null
+++ b/pkgs/matcher/test/matcher/throws_test.dart
@@ -0,0 +1,282 @@
+// Copyright (c) 2015, 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.
+
+// ignore_for_file: only_throw_errors
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:test_api/hooks_testing.dart';
+
+import '../utils_new.dart';
+
+void main() {
+ group('synchronous', () {
+ group('[throws]', () {
+ test('with a function that throws an error', () {
+ // ignore: deprecated_member_use_from_same_package
+ expect(() => throw 'oh no', throws);
+ });
+
+ test("with a function that doesn't throw", () async {
+ void local() {}
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package
+ expect(local, throws);
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith('Expected: throws\n'
+ ' Actual: <'),
+ endsWith('>\n'
+ ' Which: returned <null>\n')
+ ]));
+ });
+
+ test('with a non-function', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package
+ expect(10, throws);
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Expected: throws\n'
+ ' Actual: <10>\n'
+ ' Which: was not a Function or Future\n');
+ });
+ });
+
+ group('[throwsA]', () {
+ test('with a function that throws an identical error', () {
+ expect(() => throw 'oh no', throwsA('oh no'));
+ });
+
+ test('with a function that throws a matching error', () {
+ expect(() => throw const FormatException('bad'),
+ throwsA(isFormatException));
+ });
+
+ test("with a function that doesn't throw", () async {
+ void local() {}
+ var monitor = await TestCaseMonitor.run(() {
+ expect(local, throwsA('oh no'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: throws 'oh no'\n"
+ ' Actual: <'),
+ endsWith('>\n'
+ ' Which: returned <null>\n')
+ ]));
+ });
+
+ test('with a non-function', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(10, throwsA('oh no'));
+ });
+
+ expectTestFailed(
+ monitor,
+ "Expected: throws 'oh no'\n"
+ ' Actual: <10>\n'
+ ' Which: was not a Function or Future\n');
+ });
+
+ test('with a function that throws the wrong error', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(() => throw 'aw dang', throwsA('oh no'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: throws 'oh no'\n"
+ ' Actual: <'),
+ contains('>\n'
+ " Which: threw 'aw dang'\n"
+ ' stack'),
+ endsWith(' which is different.\n'
+ ' Expected: oh no\n'
+ ' Actual: aw dang\n'
+ ' ^\n'
+ ' Differ at offset 0\n')
+ ]));
+ });
+ });
+ });
+
+ group('asynchronous', () {
+ group('[throws]', () {
+ test('with a Future that throws an error', () {
+ // ignore: deprecated_member_use_from_same_package
+ expect(Future.error('oh no'), throws);
+ });
+
+ test("with a Future that doesn't throw", () async {
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package
+ expect(Future.value(), throws);
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith('Expected: throws\n'
+ ' Actual: <'),
+ endsWith('>\n'
+ ' Which: emitted <null>\n')
+ ]));
+ });
+
+ test('with a closure that returns a Future that throws an error', () {
+ // ignore: deprecated_member_use_from_same_package
+ expect(() => Future.error('oh no'), throws);
+ });
+
+ test("with a closure that returns a Future that doesn't throw", () async {
+ var monitor = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package
+ expect(Future.value, throws);
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith('Expected: throws\n'
+ ' Actual: <'),
+ endsWith('>\n'
+ ' Which: returned a Future that emitted <null>\n')
+ ]));
+ });
+
+ test("won't let the test end until the Future completes", () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ final completer = Completer<void>();
+ // ignore: deprecated_member_use_from_same_package
+ expect(completer.future, throws);
+ callback = () => completer.completeError('oh no');
+ });
+ await pumpEventQueue();
+ expect(monitor.state, State.running);
+ callback();
+ await monitor.onDone;
+ expectTestPassed(monitor);
+ });
+ });
+
+ group('[throwsA]', () {
+ test('with a Future that throws an identical error', () {
+ expect(Future.error('oh no'), throwsA('oh no'));
+ });
+
+ test('with a Future that throws a matching error', () {
+ expect(Future.error(const FormatException('bad')),
+ throwsA(isFormatException));
+ });
+
+ test("with a Future that doesn't throw", () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.value(), throwsA('oh no'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: throws 'oh no'\n"
+ ' Actual: <'),
+ endsWith('>\n'
+ ' Which: emitted <null>\n')
+ ]));
+ });
+
+ test('with a Future that throws the wrong error', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.error('aw dang'), throwsA('oh no'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: throws 'oh no'\n"
+ ' Actual: <'),
+ contains('>\n'
+ " Which: threw 'aw dang'\n")
+ ]));
+ });
+
+ test('with a closure that returns a Future that throws a matching error',
+ () {
+ expect(() => Future.error(const FormatException('bad')),
+ throwsA(isFormatException));
+ });
+
+ test("with a closure that returns a Future that doesn't throw", () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(Future.value, throwsA('oh no'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: throws 'oh no'\n"
+ ' Actual: <'),
+ endsWith('>\n'
+ ' Which: returned a Future that emitted <null>\n')
+ ]));
+ });
+
+ test('with closure that returns a Future that throws the wrong error',
+ () async {
+ var monitor = await TestCaseMonitor.run(() {
+ expect(() => Future.error('aw dang'), throwsA('oh no'));
+ });
+
+ expectTestFailed(
+ monitor,
+ allOf([
+ startsWith("Expected: throws 'oh no'\n"
+ ' Actual: <'),
+ contains('>\n'
+ " Which: threw 'aw dang'\n")
+ ]));
+ });
+
+ test("won't let the test end until the Future completes", () async {
+ late void Function() callback;
+ final monitor = TestCaseMonitor.start(() {
+ final completer = Completer<void>();
+ expect(completer.future, throwsA('oh no'));
+ callback = () => completer.completeError('oh no');
+ });
+ await pumpEventQueue();
+ expect(monitor.state, State.running);
+ callback();
+ await monitor.onDone;
+
+ expectTestPassed(monitor);
+ });
+
+ test("blocks expectLater's Future", () async {
+ var completer = Completer();
+ var fired = false;
+ unawaited(expectLater(completer.future, throwsArgumentError).then((_) {
+ fired = true;
+ }));
+
+ await pumpEventQueue();
+ expect(fired, isFalse);
+
+ completer.completeError(ArgumentError('oh no'));
+ await pumpEventQueue();
+ expect(fired, isTrue);
+ });
+ });
+ });
+}
diff --git a/pkgs/matcher/test/matcher/throws_type_test.dart b/pkgs/matcher/test/matcher/throws_type_test.dart
new file mode 100644
index 0000000..557a73a
--- /dev/null
+++ b/pkgs/matcher/test/matcher/throws_type_test.dart
@@ -0,0 +1,177 @@
+// Copyright (c) 2015, 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.
+
+// ignore_for_file: only_throw_errors
+
+import 'package:test/test.dart';
+import 'package:test_api/hooks_testing.dart';
+
+import '../utils_new.dart';
+
+void main() {
+ group('[throwsArgumentError]', () {
+ test('passes when a ArgumentError is thrown', () {
+ expect(() => throw ArgumentError(''), throwsArgumentError);
+ });
+
+ test('fails when a non-ArgumentError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsArgumentError);
+ });
+
+ expectTestFailed(liveTest,
+ startsWith("Expected: throws <Instance of 'ArgumentError'>"));
+ });
+ });
+
+ group('[throwsConcurrentModificationError]', () {
+ test('passes when a ConcurrentModificationError is thrown', () {
+ expect(() => throw ConcurrentModificationError(''),
+ throwsConcurrentModificationError);
+ });
+
+ test('fails when a non-ConcurrentModificationError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsConcurrentModificationError);
+ });
+
+ expectTestFailed(
+ liveTest,
+ startsWith(
+ "Expected: throws <Instance of 'ConcurrentModificationError'>"));
+ });
+ });
+
+ group('[throwsCyclicInitializationError]', () {
+ test('passes when a CyclicInitializationError is thrown', () {
+ expect(
+ () => _CyclicInitializationFailure().x,
+ // ignore: deprecated_member_use_from_same_package
+ throwsCyclicInitializationError);
+ });
+
+ test('fails when a non-CyclicInitializationError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ // ignore: deprecated_member_use_from_same_package
+ expect(() => throw Exception(), throwsCyclicInitializationError);
+ });
+
+ expectTestFailed(
+ liveTest, startsWith("Expected: throws <Instance of 'Error'>"));
+ });
+ });
+
+ group('[throwsException]', () {
+ test('passes when a Exception is thrown', () {
+ expect(() => throw Exception(''), throwsException);
+ });
+
+ test('fails when a non-Exception is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw 'oh no', throwsException);
+ });
+
+ expectTestFailed(
+ liveTest, startsWith("Expected: throws <Instance of 'Exception'>"));
+ });
+ });
+
+ group('[throwsFormatException]', () {
+ test('passes when a FormatException is thrown', () {
+ expect(() => throw const FormatException(''), throwsFormatException);
+ });
+
+ test('fails when a non-FormatException is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsFormatException);
+ });
+
+ expectTestFailed(liveTest,
+ startsWith("Expected: throws <Instance of 'FormatException'>"));
+ });
+ });
+
+ group('[throwsNoSuchMethodError]', () {
+ test('passes when a NoSuchMethodError is thrown', () {
+ expect(() {
+ // ignore: avoid_dynamic_calls
+ (1 as dynamic).notAMethodOnInt();
+ }, throwsNoSuchMethodError);
+ });
+
+ test('fails when a non-NoSuchMethodError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsNoSuchMethodError);
+ });
+
+ expectTestFailed(liveTest,
+ startsWith("Expected: throws <Instance of 'NoSuchMethodError'>"));
+ });
+ });
+
+ group('[throwsRangeError]', () {
+ test('passes when a RangeError is thrown', () {
+ expect(() => throw RangeError(''), throwsRangeError);
+ });
+
+ test('fails when a non-RangeError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsRangeError);
+ });
+
+ expectTestFailed(
+ liveTest, startsWith("Expected: throws <Instance of 'RangeError'>"));
+ });
+ });
+
+ group('[throwsStateError]', () {
+ test('passes when a StateError is thrown', () {
+ expect(() => throw StateError(''), throwsStateError);
+ });
+
+ test('fails when a non-StateError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsStateError);
+ });
+
+ expectTestFailed(
+ liveTest, startsWith("Expected: throws <Instance of 'StateError'>"));
+ });
+ });
+
+ group('[throwsUnimplementedError]', () {
+ test('passes when a UnimplementedError is thrown', () {
+ expect(() => throw UnimplementedError(''), throwsUnimplementedError);
+ });
+
+ test('fails when a non-UnimplementedError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsUnimplementedError);
+ });
+
+ expectTestFailed(liveTest,
+ startsWith("Expected: throws <Instance of 'UnimplementedError'>"));
+ });
+ });
+
+ group('[throwsUnsupportedError]', () {
+ test('passes when a UnsupportedError is thrown', () {
+ expect(() => throw UnsupportedError(''), throwsUnsupportedError);
+ });
+
+ test('fails when a non-UnsupportedError is thrown', () async {
+ var liveTest = await TestCaseMonitor.run(() {
+ expect(() => throw Exception(), throwsUnsupportedError);
+ });
+
+ expectTestFailed(liveTest,
+ startsWith("Expected: throws <Instance of 'UnsupportedError'>"));
+ });
+ });
+}
+
+class _CyclicInitializationFailure {
+ late int x = y;
+ late int y = x;
+}
diff --git a/pkgs/matcher/test/mirror_matchers_test.dart b/pkgs/matcher/test/mirror_matchers_test.dart
new file mode 100644
index 0000000..06f0df4
--- /dev/null
+++ b/pkgs/matcher/test/mirror_matchers_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2012, 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+
+@TestOn('vm')
+library;
+
+import 'package:matcher/mirror_matchers.dart';
+import 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+class C {
+ int instanceField = 1;
+ int get instanceGetter => 2;
+ static int staticField = 3;
+ static int get staticGetter => 4;
+}
+
+void main() {
+ test('hasProperty', () {
+ var foo = [3];
+ shouldPass(foo, hasProperty('length', 1));
+ shouldFail(
+ foo,
+ hasProperty('foo'),
+ 'Expected: has property "foo" '
+ 'Actual: [3] '
+ 'Which: has no property named "foo"');
+ shouldFail(
+ foo,
+ hasProperty('length', 2),
+ 'Expected: has property "length" which matches <2> '
+ 'Actual: [3] '
+ 'Which: has property "length" with value <1>');
+ var c = C();
+ shouldPass(c, hasProperty('instanceField', 1));
+ shouldPass(c, hasProperty('instanceGetter', 2));
+ shouldFail(
+ c,
+ hasProperty('staticField'),
+ 'Expected: has property "staticField" '
+ 'Actual: <Instance of \'C\'> '
+ 'Which: has a member named "staticField",'
+ ' but it is not an instance property');
+ shouldFail(
+ c,
+ hasProperty('staticGetter'),
+ 'Expected: has property "staticGetter" '
+ 'Actual: <Instance of \'C\'> '
+ 'Which: has a member named "staticGetter",'
+ ' but it is not an instance property');
+ });
+}
diff --git a/pkgs/matcher/test/never_called_test.dart b/pkgs/matcher/test/never_called_test.dart
new file mode 100644
index 0000000..4c83e39
--- /dev/null
+++ b/pkgs/matcher/test/never_called_test.dart
@@ -0,0 +1,75 @@
+// 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.
+
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+import 'package:test_api/hooks_testing.dart';
+
+import 'utils_new.dart';
+
+void main() {
+ setUpAll(() {
+ glyph.ascii = true;
+ });
+
+ test("doesn't throw if it isn't called", () async {
+ var monitor = await TestCaseMonitor.run(() {
+ const Stream.empty().listen(neverCalled);
+ });
+
+ expectTestPassed(monitor);
+ });
+
+ group("if it's called", () {
+ test('throws', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ neverCalled();
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Callback should never have been called, but it was called with no '
+ 'arguments.');
+ });
+
+ test('pretty-prints arguments', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ neverCalled(1, 'foo\nbar');
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Callback should never have been called, but it was called with:\n'
+ '* <1>\n'
+ "* 'foo\\n'\n"
+ " 'bar'");
+ });
+
+ test('keeps the test alive', () async {
+ var monitor = await TestCaseMonitor.run(() {
+ pumpEventQueue(times: 10).then(neverCalled);
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Callback should never have been called, but it was called with:\n'
+ '* <null>');
+ });
+
+ test("can't be caught", () async {
+ var monitor = await TestCaseMonitor.run(() {
+ try {
+ neverCalled();
+ } catch (_) {
+ // Do nothing.
+ }
+ });
+
+ expectTestFailed(
+ monitor,
+ 'Callback should never have been called, but it was called with '
+ 'no arguments.');
+ });
+ });
+}
diff --git a/pkgs/matcher/test/numeric_matchers_test.dart b/pkgs/matcher/test/numeric_matchers_test.dart
new file mode 100644
index 0000000..3919588
--- /dev/null
+++ b/pkgs/matcher/test/numeric_matchers_test.dart
@@ -0,0 +1,98 @@
+// Copyright (c) 2012, 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 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ test('closeTo', () {
+ shouldPass(0, closeTo(0, 1));
+ shouldPass(-1, closeTo(0, 1));
+ shouldPass(1, closeTo(0, 1));
+ shouldFail(
+ 1.001,
+ closeTo(0, 1),
+ 'Expected: a numeric value within <1> of <0> '
+ 'Actual: <1.001> '
+ 'Which: differs by <1.001>');
+ shouldFail(
+ -1.001,
+ closeTo(0, 1),
+ 'Expected: a numeric value within <1> of <0> '
+ 'Actual: <-1.001> '
+ 'Which: differs by <1.001>');
+ shouldFail(
+ 'not a num', closeTo(0, 1), endsWith('not an <Instance of \'num\'>'));
+ });
+
+ test('inInclusiveRange', () {
+ shouldFail(
+ -1,
+ inInclusiveRange(0, 2),
+ 'Expected: be in range from 0 (inclusive) to 2 (inclusive) '
+ 'Actual: <-1>');
+ shouldPass(0, inInclusiveRange(0, 2));
+ shouldPass(1, inInclusiveRange(0, 2));
+ shouldPass(2, inInclusiveRange(0, 2));
+ shouldFail(
+ 3,
+ inInclusiveRange(0, 2),
+ 'Expected: be in range from 0 (inclusive) to 2 (inclusive) '
+ 'Actual: <3>');
+ shouldFail('not a num', inInclusiveRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
+ });
+
+ test('inExclusiveRange', () {
+ shouldFail(
+ 0,
+ inExclusiveRange(0, 2),
+ 'Expected: be in range from 0 (exclusive) to 2 (exclusive) '
+ 'Actual: <0>');
+ shouldPass(1, inExclusiveRange(0, 2));
+ shouldFail(
+ 2,
+ inExclusiveRange(0, 2),
+ 'Expected: be in range from 0 (exclusive) to 2 (exclusive) '
+ 'Actual: <2>');
+ shouldFail('not a num', inExclusiveRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
+ });
+
+ test('inOpenClosedRange', () {
+ shouldFail(
+ 0,
+ inOpenClosedRange(0, 2),
+ 'Expected: be in range from 0 (exclusive) to 2 (inclusive) '
+ 'Actual: <0>');
+ shouldPass(1, inOpenClosedRange(0, 2));
+ shouldPass(2, inOpenClosedRange(0, 2));
+ shouldFail('not a num', inOpenClosedRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
+ });
+
+ test('inClosedOpenRange', () {
+ shouldPass(0, inClosedOpenRange(0, 2));
+ shouldPass(1, inClosedOpenRange(0, 2));
+ shouldFail(
+ 2,
+ inClosedOpenRange(0, 2),
+ 'Expected: be in range from 0 (inclusive) to 2 (exclusive) '
+ 'Actual: <2>');
+ shouldFail('not a num', inClosedOpenRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
+ });
+
+ group('NaN', () {
+ test('inInclusiveRange', () {
+ shouldFail(
+ double.nan,
+ inExclusiveRange(double.negativeInfinity, double.infinity),
+ 'Expected: be in range from '
+ '-Infinity (exclusive) to Infinity (exclusive) '
+ 'Actual: <NaN>');
+ });
+ });
+}
diff --git a/pkgs/matcher/test/operator_matchers_test.dart b/pkgs/matcher/test/operator_matchers_test.dart
new file mode 100644
index 0000000..f4b6d3a
--- /dev/null
+++ b/pkgs/matcher/test/operator_matchers_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2012, 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 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test, expect, throwsArgumentError;
+
+import 'test_utils.dart';
+
+void main() {
+ test('anyOf', () {
+ // with a list
+ shouldFail(
+ 0, anyOf([equals(1), equals(2)]), 'Expected: (<1> or <2>) Actual: <0>');
+ shouldPass(1, anyOf([equals(1), equals(2)]));
+
+ // with individual items
+ shouldFail(
+ 0, anyOf(equals(1), equals(2)), 'Expected: (<1> or <2>) Actual: <0>');
+ shouldPass(1, anyOf(equals(1), equals(2)));
+ });
+
+ test('allOf', () {
+ // with a list
+ shouldPass(1, allOf([lessThan(10), greaterThan(0)]));
+ shouldFail(
+ -1,
+ allOf([lessThan(10), greaterThan(0)]),
+ 'Expected: (a value less than <10> and a value greater than <0>) '
+ 'Actual: <-1> '
+ 'Which: is not a value greater than <0>');
+
+ // with individual items
+ shouldPass(1, allOf(lessThan(10), greaterThan(0)));
+ shouldFail(
+ -1,
+ allOf(lessThan(10), greaterThan(0)),
+ 'Expected: (a value less than <10> and a value greater than <0>) '
+ 'Actual: <-1> '
+ 'Which: is not a value greater than <0>');
+
+ // with maximum items
+ shouldPass(
+ 1,
+ allOf(lessThan(10), lessThan(9), lessThan(8), lessThan(7), lessThan(6),
+ lessThan(5), lessThan(4)));
+ shouldFail(
+ 4,
+ allOf(lessThan(10), lessThan(9), lessThan(8), lessThan(7), lessThan(6),
+ lessThan(5), lessThan(4)),
+ 'Expected: (a value less than <10> and a value less than <9> and a '
+ 'value less than <8> and a value less than <7> and a value less than '
+ '<6> and a value less than <5> and a value less than <4>) '
+ 'Actual: <4> '
+ 'Which: is not a value less than <4>');
+ });
+
+ test('If the first argument is a List, the rest must be null', () {
+ expect(() => allOf([], 5), throwsArgumentError);
+ expect(
+ () => anyOf([], null, null, null, null, null, 42), throwsArgumentError);
+ });
+}
diff --git a/pkgs/matcher/test/order_matchers_test.dart b/pkgs/matcher/test/order_matchers_test.dart
new file mode 100644
index 0000000..8a7c3df
--- /dev/null
+++ b/pkgs/matcher/test/order_matchers_test.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2012, 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 'package:test/test.dart';
+
+import 'test_utils.dart';
+
+void main() {
+ test('greaterThan', () {
+ shouldPass(10, greaterThan(9));
+ shouldFail(
+ 9,
+ greaterThan(10),
+ 'Expected: a value greater than <10> '
+ 'Actual: <9> '
+ 'Which: is not a value greater than <10>');
+ });
+
+ test('greaterThanOrEqualTo', () {
+ shouldPass(10, greaterThanOrEqualTo(10));
+ shouldFail(
+ 9,
+ greaterThanOrEqualTo(10),
+ 'Expected: a value greater than or equal to <10> '
+ 'Actual: <9> '
+ 'Which: is not a value greater than or equal to <10>');
+ });
+
+ test('lessThan', () {
+ shouldFail(
+ 10,
+ lessThan(9),
+ 'Expected: a value less than <9> '
+ 'Actual: <10> '
+ 'Which: is not a value less than <9>');
+ shouldPass(9, lessThan(10));
+ });
+
+ test('lessThanOrEqualTo', () {
+ shouldPass(10, lessThanOrEqualTo(10));
+ shouldFail(
+ 11,
+ lessThanOrEqualTo(10),
+ 'Expected: a value less than or equal to <10> '
+ 'Actual: <11> '
+ 'Which: is not a value less than or equal to <10>');
+ });
+
+ test('isZero', () {
+ shouldPass(0, isZero);
+ shouldFail(
+ 1,
+ isZero,
+ 'Expected: a value equal to <0> '
+ 'Actual: <1> '
+ 'Which: is not a value equal to <0>');
+ });
+
+ test('isNonZero', () {
+ shouldFail(
+ 0,
+ isNonZero,
+ 'Expected: a value not equal to <0> '
+ 'Actual: <0> '
+ 'Which: is not a value not equal to <0>');
+ shouldPass(1, isNonZero);
+ });
+
+ test('isPositive', () {
+ shouldFail(
+ -1,
+ isPositive,
+ 'Expected: a positive value '
+ 'Actual: <-1> '
+ 'Which: is not a positive value');
+ shouldFail(
+ 0,
+ isPositive,
+ 'Expected: a positive value '
+ 'Actual: <0> '
+ 'Which: is not a positive value');
+ shouldPass(1, isPositive);
+ });
+
+ test('isNegative', () {
+ shouldPass(-1, isNegative);
+ shouldFail(
+ 0,
+ isNegative,
+ 'Expected: a negative value '
+ 'Actual: <0> '
+ 'Which: is not a negative value');
+ });
+
+ test('isNonPositive', () {
+ shouldPass(-1, isNonPositive);
+ shouldPass(0, isNonPositive);
+ shouldFail(
+ 1,
+ isNonPositive,
+ 'Expected: a non-positive value '
+ 'Actual: <1> '
+ 'Which: is not a non-positive value');
+ });
+
+ test('isNonNegative', () {
+ shouldPass(1, isNonNegative);
+ shouldPass(0, isNonNegative);
+ shouldFail(
+ -1,
+ isNonNegative,
+ 'Expected: a non-negative value '
+ 'Actual: <-1> '
+ 'Which: is not a non-negative value');
+ });
+
+ group('NaN', () {
+ test('greaterThan', () {
+ shouldFail(
+ double.nan,
+ greaterThan(10),
+ 'Expected: a value greater than <10> '
+ 'Actual: <NaN> '
+ 'Which: is not a value greater than <10>');
+ shouldFail(
+ 10,
+ greaterThan(double.nan),
+ 'Expected: a value greater than <NaN> '
+ 'Actual: <10> '
+ 'Which: is not a value greater than <NaN>');
+ });
+
+ test('lessThanOrEqualTo', () {
+ shouldFail(
+ double.nan,
+ lessThanOrEqualTo(10),
+ 'Expected: a value less than or equal to <10> '
+ 'Actual: <NaN> '
+ 'Which: is not a value less than or equal to <10>');
+ shouldFail(
+ 10,
+ lessThanOrEqualTo(double.nan),
+ 'Expected: a value less than or equal to <NaN> '
+ 'Actual: <10> '
+ 'Which: is not a value less than or equal to <NaN>');
+ });
+ });
+}
diff --git a/pkgs/matcher/test/pretty_print_test.dart b/pkgs/matcher/test/pretty_print_test.dart
new file mode 100644
index 0000000..184704b
--- /dev/null
+++ b/pkgs/matcher/test/pretty_print_test.dart
@@ -0,0 +1,263 @@
+// Copyright (c) 2013, 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:collection';
+
+import 'package:matcher/matcher.dart';
+import 'package:matcher/src/pretty_print.dart';
+import 'package:test/test.dart' show group, test, expect;
+
+class DefaultToString {}
+
+class CustomToString {
+ @override
+ String toString() => 'string representation';
+}
+
+class _PrivateName {
+ @override
+ String toString() => 'string representation';
+}
+
+class _PrivateNameIterable extends IterableMixin {
+ @override
+ Iterator get iterator => [1, 2, 3].iterator;
+}
+
+void main() {
+ test('with primitive objects', () {
+ expect(prettyPrint(12), equals('<12>'));
+ expect(prettyPrint(12.13), equals('<12.13>'));
+ expect(prettyPrint(true), equals('<true>'));
+ expect(prettyPrint(null), equals('<null>'));
+ expect(prettyPrint(() => 12), matches(r'<Closure.*>'));
+ });
+
+ group('with a string', () {
+ test('containing simple characters', () {
+ expect(prettyPrint('foo'), equals("'foo'"));
+ });
+
+ test('containing newlines', () {
+ expect(
+ prettyPrint('foo\nbar\nbaz'),
+ equals("'foo\\n'\n"
+ " 'bar\\n'\n"
+ " 'baz'"));
+ });
+
+ test('containing escapable characters', () {
+ expect(prettyPrint("foo\rbar\tbaz'qux\v"),
+ equals(r"'foo\rbar\tbaz\'qux\v'"));
+ });
+ });
+
+ group('with an iterable', () {
+ test('containing primitive objects', () {
+ expect(prettyPrint([1, true, 'foo']), equals("[1, true, 'foo']"));
+ });
+
+ test('containing a multiline string', () {
+ expect(
+ prettyPrint(['foo', 'bar\nbaz\nbip', 'qux']),
+ equals('[\n'
+ " 'foo',\n"
+ " 'bar\\n'\n"
+ " 'baz\\n'\n"
+ " 'bip',\n"
+ " 'qux'\n"
+ ']'));
+ });
+
+ test('containing a matcher', () {
+ expect(prettyPrint(['foo', endsWith('qux')]),
+ equals("['foo', <a string ending with 'qux'>]"));
+ });
+
+ test("that's under maxLineLength", () {
+ expect(prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxLineLength: 30),
+ equals('[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'));
+ });
+
+ test("that's over maxLineLength", () {
+ expect(
+ prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxLineLength: 29),
+ equals('[\n'
+ ' 0,\n'
+ ' 1,\n'
+ ' 2,\n'
+ ' 3,\n'
+ ' 4,\n'
+ ' 5,\n'
+ ' 6,\n'
+ ' 7,\n'
+ ' 8,\n'
+ ' 9\n'
+ ']'));
+ });
+
+ test('factors indentation into maxLineLength', () {
+ expect(
+ prettyPrint([
+ 'foo\nbar',
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ ], maxLineLength: 30),
+ equals('[\n'
+ " 'foo\\n'\n"
+ " 'bar',\n"
+ ' [\n'
+ ' 0,\n'
+ ' 1,\n'
+ ' 2,\n'
+ ' 3,\n'
+ ' 4,\n'
+ ' 5,\n'
+ ' 6,\n'
+ ' 7,\n'
+ ' 8,\n'
+ ' 9\n'
+ ' ]\n'
+ ']'));
+ });
+
+ test("that's under maxItems", () {
+ expect(prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxItems: 10),
+ equals('[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'));
+ });
+
+ test("that's over maxItems", () {
+ expect(prettyPrint([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxItems: 9),
+ equals('[0, 1, 2, 3, 4, 5, 6, 7, ...]'));
+ });
+
+ test("that's recursive", () {
+ var list = <dynamic>[1, 2, 3];
+ list.add(list);
+ expect(prettyPrint(list), equals('[1, 2, 3, (recursive)]'));
+ });
+ });
+
+ group('with a map', () {
+ test('containing primitive objects', () {
+ expect(prettyPrint({'foo': 1, 'bar': true}),
+ equals("{'foo': 1, 'bar': true}"));
+ });
+
+ test('containing a multiline string key', () {
+ expect(
+ prettyPrint({'foo\nbar': 1, 'bar': true}),
+ equals('{\n'
+ " 'foo\\n'\n"
+ " 'bar': 1,\n"
+ " 'bar': true\n"
+ '}'));
+ });
+
+ test('containing a multiline string value', () {
+ expect(
+ prettyPrint({'foo': 'bar\nbaz', 'qux': true}),
+ equals('{\n'
+ " 'foo': 'bar\\n'\n"
+ " 'baz',\n"
+ " 'qux': true\n"
+ '}'));
+ });
+
+ test('containing a multiline string key/value pair', () {
+ expect(
+ prettyPrint({'foo\nbar': 'baz\nqux'}),
+ equals('{\n'
+ " 'foo\\n'\n"
+ " 'bar': 'baz\\n'\n"
+ " 'qux'\n"
+ '}'));
+ });
+
+ test('containing a matcher key', () {
+ expect(prettyPrint({endsWith('bar'): 'qux'}),
+ equals("{<a string ending with 'bar'>: 'qux'}"));
+ });
+
+ test('containing a matcher value', () {
+ expect(prettyPrint({'foo': endsWith('qux')}),
+ equals("{'foo': <a string ending with 'qux'>}"));
+ });
+
+ test("that's under maxLineLength", () {
+ expect(prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxLineLength: 32),
+ equals("{'0': 1, '2': 3, '4': 5, '6': 7}"));
+ });
+
+ test("that's over maxLineLength", () {
+ expect(
+ prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxLineLength: 31),
+ equals('{\n'
+ " '0': 1,\n"
+ " '2': 3,\n"
+ " '4': 5,\n"
+ " '6': 7\n"
+ '}'));
+ });
+
+ test('factors indentation into maxLineLength', () {
+ expect(
+ prettyPrint([
+ 'foo\nbar',
+ {'0': 1, '2': 3, '4': 5, '6': 7}
+ ], maxLineLength: 32),
+ equals('[\n'
+ " 'foo\\n'\n"
+ " 'bar',\n"
+ ' {\n'
+ " '0': 1,\n"
+ " '2': 3,\n"
+ " '4': 5,\n"
+ " '6': 7\n"
+ ' }\n'
+ ']'));
+ });
+
+ test("that's under maxItems", () {
+ expect(prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxItems: 4),
+ equals("{'0': 1, '2': 3, '4': 5, '6': 7}"));
+ });
+
+ test("that's over maxItems", () {
+ expect(prettyPrint({'0': 1, '2': 3, '4': 5, '6': 7}, maxItems: 3),
+ equals("{'0': 1, '2': 3, ...}"));
+ });
+ });
+ group('with an object', () {
+ test('with a default [toString]', () {
+ expect(prettyPrint(DefaultToString()),
+ equals("<Instance of 'DefaultToString'>"));
+ });
+
+ test('with a custom [toString]', () {
+ expect(prettyPrint(CustomToString()),
+ equals('CustomToString:<string representation>'));
+ });
+
+ test('with a custom [toString] and a private name', () {
+ expect(prettyPrint(_PrivateName()),
+ equals('_PrivateName:<string representation>'));
+ });
+ });
+
+ group('with an iterable', () {
+ test("that's not a list", () {
+ expect(prettyPrint([1, 2, 3, 4].map((n) => n * 2)),
+ equals('MappedListIterable<int, int>:[2, 4, 6, 8]'));
+ });
+
+ test("that's not a list and has a private name", () {
+ expect(prettyPrint(_PrivateNameIterable()),
+ equals('_PrivateNameIterable:[1, 2, 3]'));
+ });
+ });
+
+ test('Type', () {
+ expect(prettyPrint(''.runtimeType), 'Type:<String>');
+ });
+}
diff --git a/pkgs/matcher/test/stream_matcher_test.dart b/pkgs/matcher/test/stream_matcher_test.dart
new file mode 100644
index 0000000..c4af666
--- /dev/null
+++ b/pkgs/matcher/test/stream_matcher_test.dart
@@ -0,0 +1,358 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/test.dart';
+
+import 'utils_new.dart';
+
+void main() {
+ setUpAll(() {
+ glyph.ascii = true;
+ });
+
+ late Stream stream;
+ late StreamQueue queue;
+ late Stream errorStream;
+ late StreamQueue errorQueue;
+ setUp(() {
+ stream = Stream.fromIterable([1, 2, 3, 4, 5]);
+ queue = StreamQueue(Stream.fromIterable([1, 2, 3, 4, 5]));
+ errorStream = Stream.fromFuture(Future.error('oh no!', StackTrace.current));
+ errorQueue = StreamQueue(
+ Stream.fromFuture(Future.error('oh no!', StackTrace.current)));
+ });
+
+ group('emits()', () {
+ test('matches the first event of a Stream', () {
+ expect(stream, emits(1));
+ });
+
+ test('rejects the first event of a Stream', () {
+ expect(
+ expectLater(stream, emits(2)),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should emit an event that <2>\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n')
+ ])));
+ });
+
+ test('matches and consumes the next event of a StreamQueue', () {
+ expect(queue, emits(1));
+ expect(queue.next, completion(equals(2)));
+ expect(queue, emits(3));
+ expect(queue.next, completion(equals(4)));
+ });
+
+ test('rejects and does not consume the first event of a StreamQueue', () {
+ expect(
+ expectLater(queue, emits(2)),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should emit an event that <2>\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n')
+ ])));
+
+ expect(queue, emits(1));
+ });
+
+ test('rejects an empty stream', () {
+ expect(
+ expectLater(const Stream.empty(), emits(1)),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should emit an event that <1>\n'),
+ endsWith(' Which: emitted x Stream closed.\n')
+ ])));
+ });
+
+ test('forwards a stream error', () {
+ expect(expectLater(errorStream, emits(1)), throwsA('oh no!'));
+ });
+
+ test('wraps a normal matcher', () {
+ expect(queue, emits(lessThan(5)));
+ expect(expectLater(queue, emits(greaterThan(5))),
+ throwsTestFailure(anything));
+ });
+
+ test('returns a StreamMatcher as-is', () {
+ expect(queue, emits(emitsThrough(4)));
+ expect(queue, emits(5));
+ });
+ });
+
+ group('emitsDone', () {
+ test('succeeds for an empty stream', () {
+ expect(const Stream.empty(), emitsDone);
+ });
+
+ test('fails for a stream with events', () {
+ expect(
+ expectLater(stream, emitsDone),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should be done\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n')
+ ])));
+ });
+ });
+
+ group('emitsError()', () {
+ test('consumes a matching error', () {
+ expect(errorQueue, emitsError('oh no!'));
+ expect(errorQueue.hasNext, completion(isFalse));
+ });
+
+ test('fails for a non-matching error', () {
+ expect(
+ expectLater(errorStream, emitsError('oh heck')),
+ throwsTestFailure(allOf([
+ startsWith("Expected: should emit an error that 'oh heck'\n"),
+ contains(' Which: emitted ! oh no!\n'),
+ contains(' x Stream closed.\n'
+ " which threw 'oh no!'\n"
+ ' stack '),
+ endsWith(' which is different.\n'
+ ' Expected: oh heck\n'
+ ' Actual: oh no!\n'
+ ' ^\n'
+ ' Differ at offset 3\n')
+ ])));
+ });
+
+ test('fails for a stream with events', () {
+ expect(
+ expectLater(stream, emitsDone),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should be done\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n')
+ ])));
+ });
+ });
+
+ group('mayEmit()', () {
+ test('consumes a matching event', () {
+ expect(queue, mayEmit(1));
+ expect(queue, emits(2));
+ });
+
+ test('allows a non-matching event', () {
+ expect(queue, mayEmit('fish'));
+ expect(queue, emits(1));
+ });
+ });
+
+ group('emitsAnyOf()', () {
+ test('consumes an event that matches a matcher', () {
+ expect(queue, emitsAnyOf([2, 1, 3]));
+ expect(queue, emits(2));
+ });
+
+ test('consumes as many events as possible', () {
+ expect(
+ queue,
+ emitsAnyOf([
+ 1,
+ emitsInOrder([1, 2]),
+ emitsInOrder([1, 2, 3])
+ ]));
+
+ expect(queue, emits(4));
+ });
+
+ test('fails if no matchers match', () {
+ expect(
+ expectLater(stream, emitsAnyOf([2, 3, 4])),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should do one of the following:\n'
+ ' * emit an event that <2>\n'
+ ' * emit an event that <3>\n'
+ ' * emit an event that <4>\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n'
+ ' which failed all options:\n'
+ ' * failed to emit an event that <2>\n'
+ ' * failed to emit an event that <3>\n'
+ ' * failed to emit an event that <4>\n')
+ ])));
+ });
+
+ test('allows an error if any matcher matches', () {
+ expect(errorStream, emitsAnyOf([1, 2, emitsError('oh no!')]));
+ });
+
+ test('rethrows an error if no matcher matches', () {
+ expect(
+ expectLater(errorStream, emitsAnyOf([1, 2, 3])), throwsA('oh no!'));
+ });
+ });
+
+ group('emitsInOrder()', () {
+ test('consumes matching events', () {
+ expect(queue, emitsInOrder([1, 2, emitsThrough(4)]));
+ expect(queue, emits(5));
+ });
+
+ test("fails if the matchers don't match in order", () {
+ expect(
+ expectLater(queue, emitsInOrder([1, 3, 2])),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should do the following in order:\n'
+ ' * emit an event that <1>\n'
+ ' * emit an event that <3>\n'
+ ' * emit an event that <2>\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n'
+ " which didn't emit an event that <3>\n")
+ ])));
+ });
+ });
+
+ group('emitsThrough()', () {
+ test('consumes events including those matching the matcher', () {
+ expect(queue, emitsThrough(emitsInOrder([3, 4])));
+ expect(queue, emits(5));
+ });
+
+ test('consumes the entire queue with emitsDone', () {
+ expect(queue, emitsThrough(emitsDone));
+ expect(queue.hasNext, completion(isFalse));
+ });
+
+ test('fails if the queue never matches the matcher', () {
+ expect(
+ expectLater(queue, emitsThrough(6)),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should eventually emit an event that <6>\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n'
+ ' which never did emit an event that <6>\n')
+ ])));
+ });
+ });
+
+ group('mayEmitMultiple()', () {
+ test('consumes multiple instances of the given matcher', () {
+ expect(queue, mayEmitMultiple(lessThan(3)));
+ expect(queue, emits(3));
+ });
+
+ test('consumes zero instances of the given matcher', () {
+ expect(queue, mayEmitMultiple(6));
+ expect(queue, emits(1));
+ });
+
+ test("doesn't rethrow errors", () {
+ expect(errorQueue, mayEmitMultiple(1));
+ expect(errorQueue, emitsError('oh no!'));
+ });
+ });
+
+ group('neverEmits()', () {
+ test('succeeds if the event never matches', () {
+ expect(queue, neverEmits(6));
+ expect(queue, emits(1));
+ });
+
+ test('fails if the event matches', () {
+ expect(
+ expectLater(stream, neverEmits(4)),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should never emit an event that <4>\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n'
+ ' which after 3 events did emit an event that <4>\n')
+ ])));
+ });
+
+ test('fails if emitsDone matches', () {
+ expect(expectLater(stream, neverEmits(emitsDone)),
+ throwsTestFailure(anything));
+ });
+
+ test("doesn't rethrow errors", () {
+ expect(errorQueue, neverEmits(6));
+ expect(errorQueue, emitsError('oh no!'));
+ });
+ });
+
+ group('emitsInAnyOrder()', () {
+ test('consumes events that match in any order', () {
+ expect(queue, emitsInAnyOrder([3, 1, 2]));
+ expect(queue, emits(4));
+ });
+
+ test("fails if the events don't match in any order", () {
+ expect(
+ expectLater(stream, emitsInAnyOrder([4, 1, 2])),
+ throwsTestFailure(allOf([
+ startsWith('Expected: should do the following in any order:\n'
+ ' * emit an event that <4>\n'
+ ' * emit an event that <1>\n'
+ ' * emit an event that <2>\n'),
+ endsWith(' Which: emitted * 1\n'
+ ' * 2\n'
+ ' * 3\n'
+ ' * 4\n'
+ ' * 5\n'
+ ' x Stream closed.\n')
+ ])));
+ });
+
+ test("doesn't rethrow if some ordering matches", () {
+ expect(errorQueue, emitsInAnyOrder([emitsDone, emitsError('oh no!')]));
+ });
+
+ test('rethrows if no ordering matches', () {
+ expect(
+ expectLater(errorQueue, emitsInAnyOrder([1, emitsError('oh no!')])),
+ throwsA('oh no!'));
+ });
+ });
+
+ test('A custom StreamController doesn\'t hang on close', () async {
+ var controller = StreamController<void>();
+ var done = expectLater(controller.stream, emits(null));
+ controller.add(null);
+ await done;
+ await controller.close();
+ });
+}
diff --git a/pkgs/matcher/test/string_matchers_test.dart b/pkgs/matcher/test/string_matchers_test.dart
new file mode 100644
index 0000000..be9e768
--- /dev/null
+++ b/pkgs/matcher/test/string_matchers_test.dart
@@ -0,0 +1,151 @@
+// Copyright (c) 2012, 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 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test, expect;
+
+import 'test_utils.dart';
+
+void main() {
+ test('Reports mismatches in whitespace and escape sequences', () {
+ shouldFail('before\nafter', equals('before\\nafter'),
+ contains('Differ at offset 7'));
+ });
+
+ test('Retains outer matcher mismatch text', () {
+ shouldFail(
+ {'word': 'thing'},
+ containsPair('word', equals('notthing')),
+ allOf([
+ contains("contains key 'word' but with value is different"),
+ contains('Differ at offset 0')
+ ]));
+ });
+
+ test('collapseWhitespace', () {
+ var source = '\t\r\n hello\t\r\n world\r\t \n';
+ expect(collapseWhitespace(source), 'hello world');
+ });
+
+ test('isEmpty', () {
+ shouldPass('', isEmpty);
+ shouldFail(null, isEmpty, startsWith('Expected: empty Actual: <null>'));
+ shouldFail(0, isEmpty, startsWith('Expected: empty Actual: <0>'));
+ shouldFail('a', isEmpty, startsWith("Expected: empty Actual: 'a'"));
+ });
+
+ // Regression test for: https://code.google.com/p/dart/issues/detail?id=21562
+ test('isNot(isEmpty)', () {
+ shouldPass('a', isNot(isEmpty));
+ shouldFail('', isNot(isEmpty), 'Expected: not empty Actual: \'\'');
+ shouldFail(null, isNot(isEmpty),
+ startsWith('Expected: not empty Actual: <null>'));
+ });
+
+ test('isNotEmpty', () {
+ shouldFail('', isNotEmpty, startsWith("Expected: non-empty Actual: ''"));
+ shouldFail(
+ null, isNotEmpty, startsWith('Expected: non-empty Actual: <null>'));
+ shouldFail(0, isNotEmpty, startsWith('Expected: non-empty Actual: <0>'));
+ shouldPass('a', isNotEmpty);
+ });
+
+ test('equalsIgnoringCase', () {
+ shouldPass('hello', equalsIgnoringCase('HELLO'));
+ shouldFail('hi', equalsIgnoringCase('HELLO'),
+ "Expected: 'HELLO' ignoring case Actual: 'hi'");
+ shouldFail(42, equalsIgnoringCase('HELLO'),
+ endsWith('not an <Instance of \'String\'>'));
+ });
+
+ test('equalsIgnoringWhitespace', () {
+ shouldPass(' hello world ', equalsIgnoringWhitespace('hello world'));
+ shouldFail(
+ ' helloworld ',
+ equalsIgnoringWhitespace('hello world'),
+ "Expected: 'hello world' ignoring whitespace "
+ "Actual: ' helloworld ' "
+ "Which: is 'helloworld' with whitespace compressed");
+ shouldFail(42, equalsIgnoringWhitespace('HELLO'),
+ endsWith('not an <Instance of \'String\'>'));
+ });
+
+ test('startsWith', () {
+ shouldPass('hello', startsWith(''));
+ shouldPass('hello', startsWith('hell'));
+ shouldPass('hello', startsWith('hello'));
+ shouldFail(
+ 'hello',
+ startsWith('hello '),
+ "Expected: a string starting with 'hello ' "
+ "Actual: 'hello'");
+ shouldFail(
+ 42, startsWith('hello '), endsWith('not an <Instance of \'String\'>'));
+ });
+
+ test('endsWith', () {
+ shouldPass('hello', endsWith(''));
+ shouldPass('hello', endsWith('lo'));
+ shouldPass('hello', endsWith('hello'));
+ shouldFail(
+ 'hello',
+ endsWith(' hello'),
+ "Expected: a string ending with ' hello' "
+ "Actual: 'hello'");
+ shouldFail(
+ 42, startsWith('hello '), endsWith('not an <Instance of \'String\'>'));
+ });
+
+ test('contains', () {
+ shouldPass('hello', contains(''));
+ shouldPass('hello', contains('h'));
+ shouldPass('hello', contains('o'));
+ shouldPass('hello', contains('hell'));
+ shouldPass('hello', contains('hello'));
+ shouldFail('hello', contains(' '),
+ "Expected: contains ' ' Actual: 'hello' Which: does not contain ' '");
+ });
+
+ test('stringContainsInOrder', () {
+ shouldPass('goodbye cruel world', stringContainsInOrder(['']));
+ shouldPass('goodbye cruel world', stringContainsInOrder(['goodbye']));
+ shouldPass('goodbye cruel world', stringContainsInOrder(['cruel']));
+ shouldPass('goodbye cruel world', stringContainsInOrder(['world']));
+ shouldPass(
+ 'goodbye cruel world', stringContainsInOrder(['good', 'bye', 'world']));
+ shouldPass(
+ 'goodbye cruel world', stringContainsInOrder(['goodbye', 'cruel']));
+ shouldPass(
+ 'goodbye cruel world', stringContainsInOrder(['cruel', 'world']));
+ shouldPass('goodbye cruel world',
+ stringContainsInOrder(['goodbye', 'cruel', 'world']));
+ shouldPass(
+ 'foo', stringContainsInOrder(['f', '', '', '', 'o', '', '', 'o']));
+
+ shouldFail(
+ 'abc',
+ stringContainsInOrder(['ab', 'bc']),
+ "Expected: a string containing 'ab', 'bc' in order "
+ "Actual: 'abc'");
+ shouldFail(
+ 'hello',
+ stringContainsInOrder(['hello', 'hello']),
+ "Expected: a string containing 'hello', 'hello' in order "
+ "Actual: 'hello'");
+ shouldFail(
+ 'goodbye cruel world',
+ stringContainsInOrder(['goo', 'cruel', 'bye']),
+ "Expected: a string containing 'goo', 'cruel', 'bye' in order "
+ "Actual: 'goodbye cruel world'");
+ });
+
+ test('matches', () {
+ shouldPass('c0d', matches('[a-z][0-9][a-z]'));
+ shouldPass('c0d', matches(RegExp('[a-z][0-9][a-z]')));
+ shouldFail('cOd', matches('[a-z][0-9][a-z]'),
+ "Expected: match '[a-z][0-9][a-z]' Actual: 'cOd'");
+ shouldFail(42, matches('[a-z][0-9][a-z]'),
+ endsWith('not an <Instance of \'String\'>'));
+ });
+}
diff --git a/pkgs/matcher/test/test_utils.dart b/pkgs/matcher/test/test_utils.dart
new file mode 100644
index 0000000..67b61a1
--- /dev/null
+++ b/pkgs/matcher/test/test_utils.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2012, 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 'package:test/test.dart';
+
+void shouldFail(Object? value, Matcher matcher, Object? expected) {
+ var failed = false;
+ try {
+ expect(value, matcher);
+ } on TestFailure catch (err) {
+ failed = true;
+
+ var errorString = err.message;
+
+ if (expected is String) {
+ expect(errorString, equalsIgnoringWhitespace(expected));
+ } else {
+ expect(errorString?.replaceAll('\n', ''), expected);
+ }
+ }
+
+ expect(failed, isTrue, reason: 'Expected to fail.');
+}
+
+void shouldPass(Object? value, Matcher matcher) {
+ expect(value, matcher);
+}
+
+void doesNotThrow() {}
+void doesThrow() {
+ throw StateError('X');
+}
+
+class Widget {
+ int? price;
+}
+
+class SimpleIterable extends Iterable<int> {
+ final int count;
+
+ SimpleIterable(this.count);
+
+ @override
+ Iterator<int> get iterator => _SimpleIterator(count);
+}
+
+class _SimpleIterator implements Iterator<int> {
+ int _count;
+ int _current;
+
+ _SimpleIterator(this._count) : _current = -1;
+
+ @override
+ bool moveNext() {
+ if (_count > 0) {
+ _current = _count;
+ _count--;
+ return true;
+ }
+ _current = -1;
+ return false;
+ }
+
+ @override
+ int get current => _current;
+}
diff --git a/pkgs/matcher/test/type_matcher_test.dart b/pkgs/matcher/test/type_matcher_test.dart
new file mode 100644
index 0000000..99d4459
--- /dev/null
+++ b/pkgs/matcher/test/type_matcher_test.dart
@@ -0,0 +1,66 @@
+// Copyright (c) 2012, 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.
+
+// ignore_for_file: deprecated_member_use_from_same_package
+import 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test, group;
+
+import 'test_utils.dart';
+
+void main() {
+ _test(isMap, {}, name: 'Map');
+ _test(isList, [], name: 'List');
+ _test(isArgumentError, ArgumentError());
+ _test(isCastError, TypeError());
+ _test<Exception>(isException, const FormatException());
+ _test(isFormatException, const FormatException());
+ _test(isStateError, StateError('oops'));
+ _test(isRangeError, RangeError('oops'));
+ _test(isUnimplementedError, UnimplementedError('oops'));
+ _test(isUnsupportedError, UnsupportedError('oops'));
+ _test(isConcurrentModificationError, ConcurrentModificationError());
+ _test(isCyclicInitializationError, Error());
+ _test<NoSuchMethodError?>(isNoSuchMethodError, null,
+ name: 'NoSuchMethodError');
+ _test(isNullThrownError, TypeError());
+
+ group('custom `TypeMatcher`', () {
+ _test(const isInstanceOf<String>(), 'hello');
+ _test(const _StringMatcher(), 'hello');
+ _test(const TypeMatcher<String>(), 'hello');
+ _test(isA<String>(), 'hello');
+ });
+}
+
+void _test<T>(Matcher typeMatcher, T matchingInstance, {String? name}) {
+ name ??= T.toString();
+ group('for `$name`', () {
+ if (matchingInstance != null) {
+ test('succeeds', () {
+ shouldPass(matchingInstance, typeMatcher);
+ });
+ }
+
+ test('fails', () {
+ shouldFail(
+ const _TestType(),
+ typeMatcher,
+ "Expected: <Instance of '$name'> Actual: <Instance of '_TestType'>"
+ " Which: is not an instance of '$name'",
+ );
+ });
+ });
+}
+
+// Validate that existing implementations continue to work.
+class _StringMatcher extends TypeMatcher {
+ const _StringMatcher() : super('String');
+
+ @override
+ bool matches(dynamic item, Map matchState) => item is String;
+}
+
+class _TestType {
+ const _TestType();
+}
diff --git a/pkgs/matcher/test/utils_new.dart b/pkgs/matcher/test/utils_new.dart
new file mode 100644
index 0000000..7d85a02
--- /dev/null
+++ b/pkgs/matcher/test/utils_new.dart
@@ -0,0 +1,49 @@
+// 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 'package:matcher/expect.dart';
+import 'package:test_api/hooks_testing.dart';
+
+/// Asserts that [monitor] has completed and passed.
+///
+/// If the test had any errors, they're surfaced nicely into the outer test.
+void expectTestPassed(TestCaseMonitor monitor) {
+ // Since the test is expected to pass, we forward any current or future errors
+ // to the running test, because they're definitely unexpected and it is most
+ // useful for the error to point directly to the throw point.
+ for (var error in monitor.errors) {
+ Zone.current.handleUncaughtError(error.error, error.stackTrace);
+ }
+ monitor.onError.listen((error) {
+ Zone.current.handleUncaughtError(error.error, error.stackTrace);
+ });
+
+ expect(monitor.state, State.passed);
+}
+
+/// Asserts that [monitor] failed with a single [TestFailure] whose message
+/// matches [message].
+void expectTestFailed(TestCaseMonitor monitor, Object? message) {
+ expect(monitor.state, State.failed);
+ expect(monitor.errors, [isAsyncError(isTestFailure(message))]);
+}
+
+/// Returns a matcher that matches a [AsyncError] with an `error` field matching
+/// [errorMatcher].
+Matcher isAsyncError(Matcher errorMatcher) =>
+ isA<AsyncError>().having((e) => e.error, 'error', errorMatcher);
+
+/// Returns a matcher that matches a [TestFailure] with the given [message].
+///
+/// [message] can be a string or a [Matcher].
+Matcher isTestFailure(Object? message) => const TypeMatcher<TestFailure>()
+ .having((e) => e.message, 'message', message);
+
+/// Returns a matcher that matches a callback or Future that throws a
+/// [TestFailure] with the given [message].
+///
+/// [message] can be a string or a [Matcher].
+Matcher throwsTestFailure(Object? message) => throwsA(isTestFailure(message));
diff --git a/pkgs/test/.test_config b/pkgs/test/.test_config
new file mode 100644
index 0000000..412fc5c
--- /dev/null
+++ b/pkgs/test/.test_config
@@ -0,0 +1,3 @@
+{
+ "test_package": true
+}
\ No newline at end of file
diff --git a/pkgs/test/CHANGELOG.md b/pkgs/test/CHANGELOG.md
new file mode 100644
index 0000000..afe8dec
--- /dev/null
+++ b/pkgs/test/CHANGELOG.md
@@ -0,0 +1,1836 @@
+## 1.25.12
+
+* Fix hang when running multiple precompiled browser tests.
+
+## 1.25.11
+
+* Update to be forward compatible with `package:shelf_web_socket` version `3.x`.
+
+## 1.25.10
+
+* Update the `package:vm_service` constraint to allow version `15.x`.
+
+## 1.25.9
+
+* Allow `analyzer: '>=6.0.0 <8.0.0'`
+* Fix dart2wasm tests on windows.
+* Increase SDK constraint to ^3.5.0.
+* Support running Node.js tests compiled with dart2wasm.
+* Allow `firefox` or `firefox-bin` executable name on macOS.
+
+## 1.25.8
+
+* Increase SDK constraint to ^3.4.0.
+
+## 1.25.7
+
+* Enable asserts for `dart2wasm` tests.
+
+## 1.25.6
+
+* Point API doc links to `package:test` canonical libraries.
+* Fix testing with `dart2wasm` - use `dart compile wasm` instead of depending on
+ SDK internals
+* Update min SDK constraint to 3.2.0.
+
+## 1.25.5
+
+* Update the `package:web_socket_channel` version constraint to allow `3.x`.
+* Update the `package:shelf_web_socket` version constraint to allow `2.x`.
+
+## 1.25.4
+
+* Add `@doNotSubmit` to more declarations of the `solo` parameter.
+
+## 1.25.3
+
+* Remove outdated StreamMatcher link from README table of contents.
+* Document the silent reporter in CLI help output.
+* Support enabling experiments with the dart2wasm compiler.
+* Added [`@doNotSubmit`](https://pub.dev/documentation/meta/latest/meta/doNotSubmit-constant.html) to `test(solo: ...)` and `group(solo: ...)`. In
+ practice, this means that code that was relying on ignoring deprecation
+ warnings and using `solo` or `group` with a `skip` parameter will now fail if
+ `dart analyze --fatal-infos` (or similar) is enabled.
+
+## 1.25.2
+
+* Fix a bug running browser tests with paths containing windows directory
+ separator follow by a character which is an invalid Dart string escape
+ sequence.
+
+## 1.25.1
+
+* Fix a bug where in precompiled mode, html files for tests were no longer
+ created.
+* Support the latest version of `package:js`.
+* Document the silent reporter in CLI help output.
+
+## 1.25.0
+
+* Handle paths with leading `/` when spawning test isolates.
+* Add support for the `dart2wasm` compiler in chrome and firefox.
+* **BREAKING**: Remove the `experimental-chrome-wasm` platform, you can now use
+ `-p chrome -c dart2wasm` instead.
+ * Note that this has always been advertised as a change that would happen in a
+ future non-breaking release.
+* **BREAKING**:Dropped support for `--pub-serve` which has long not been tested
+ or supported.
+ * We do not anticipate much if any actual breakage or existing usage of this
+ feature, which is why we are making this change in a non-breaking release.
+ * If you do require this feature, file an issue and we can look at adding it
+ back.
+* **BREAKING**: Fully remove support for Internet Explorer.
+* Fix running of tests defined under `lib/` with relative imports to other
+ libraries in the package.
+
+## 1.24.9
+
+* Update the vm_service constraint to allow version `13.x`.
+
+## 1.24.8
+
+* Remove spurious deprecation during autocomplete for `setUp` and `tearDown`.
+
+## 1.24.7
+
+* Simplify the initialization of the per-suite message channel within browser
+ tests. See https://github.com/dart-lang/test/issues/2065
+* Add a timeout to browser test suite loads.
+* Fix running of browser tests that use deferred loaded libraries.
+
+## 1.24.6
+
+* Fix communication failures between minified test apps and the non-minified
+ host app.
+* Add support for discontinuing after the first failing test with `--fail-fast`.
+
+## 1.24.5
+
+* Change `compiling <path>` to `loading <path>` message in all cases. Surface
+ the "loading" messages in the situations where previously only the
+ "compiling" message would be used.
+* Support browser tests where the frame creates the message channel.
+
+## 1.24.4
+
+* Drop support for null unsafe Dart, bump SDK constraint to `3.0.0`.
+* Make some annotation classes `final`: `OnPlatform`, `Retry`, `Skip`, `Tags`,
+ `TestOn`, `Timeout`.
+* Fix the `root_` fields in the JSON reporter when running a test on Windows
+ with an absolute path.
+* Add support for `SAFARI_EXECUTABLE`, `FIREFOX_EXECUTABLE` and
+ `MS_EDGE_EXECUTABLE` for custom browser installations.
+* Allow the latest analyzer (6.x.x).
+* Add `MOZ_AUTOMATION=1` environmental variable to Firefox runner, to make
+ launcher process on Windows wait for browser exit.
+
+## 1.24.3
+
+* Fix compatibility with wasm number semantics.
+
+## 1.24.2
+
+* Copy an existing nonce from a script on the test HTML page to the script
+ created by the test runner host javascript. This only impacts environments
+ testing with custom HTML that includes a nonce.
+* Support the Microsoft Edge browser (use the `edge` platform in your test
+ configuration file or `-p edge` on the command line).
+
+## 1.24.1
+
+* Handle a missing `'compiler'` value when running a test compiled against a
+ newer `test_api` than the runner back end is using. The expectation was that
+ the json protocol is only used across packages compatible with the same major
+ version of the `test_api` package, but `flutter test` does not check the
+ version of packages in the pub solve for user test code.
+
+## 1.24.0
+
+* Support the `--compiler` flag, which can be used to configure which compiler
+ to use.
+ * To specify a compiler by platform, the argument supports platform selectors
+ through this syntax `[<platform>:]<compiler>`. For example the command line
+ argument `--compiler vm:source` would run all vm tests from source instead
+ of compiling to kernel first.
+ * If no given compiler is compatible for a platform, it will use its default
+ compiler instead.
+* Add support for running tests as native executables (vm platform only).
+ * You can run tests this way with `--compiler exe`.
+* Support compiler identifiers in platform selectors.
+* List the supported compilers for each platform in the usage text.
+* Update all reporters to print the compiler along with the platform name
+ when configured to print the platform. Extend the logic for printing platofrm
+ information to do so if any compilers are explicitly configured.
+* Deprecate `--use-data-isolate-strategy`. It is now an alias for `-c vm:source`
+ which is roughly equivalent. If this is breaking for you please file an issue.
+
+## 1.23.1
+
+* Fix running paths by absolute path (with drive letter) on windows.
+
+## 1.23.0
+
+* Avoid empty expandable groups for tests without extra output in Github
+ reporter.
+* Add support for CHROME_EXECUTABLE environment variable. This overrides any
+ config file settings.
+* Support running tests by absolute file uri.
+
+## 1.22.2
+
+* Don't run `tearDown` until the test body and outstanding work is complete,
+ even if the test has already failed.
+* Change URL secrets for browser tests to always be alphanumeric characters.
+
+## 1.22.1
+
+* Add documentation for the `--ignore-timeouts` argument.
+* Merge command lines args repeating the same test path to run the suite one
+ time with all the test cases across the different arguments.
+* Fix VM tests which run after some test has changed the working directory.
+ There are still issues with browser tests after changing directory.
+* Deprecate `throwsNullThrownError`, use `throwsA(isA<TypeError>())` instead. The
+ implementation has been changed to ease migrations.
+* Deprecate `throwsCyclicInitializationError` and replace the implementation
+ with `Throws(TypeMatcher<Error>())`. The specific exception no longer exists
+ and there is no guarantee about what type of error will be thrown.
+
+## 1.22.0
+
+* Fix an issue with the github reporter where tests that fail asynchronously
+ after they've completed would show up as succeeded tests.
+* Add the `experimental-chrome-wasm` platform. This is very unstable and will
+ eventually be deleted, to be replaced by a `--compiler` flag. See
+ https://github.com/dart-lang/test/issues/1776 for more information on future
+ plans.
+
+## 1.21.7
+
+* Support `package:matcher` version `0.12.13`.
+
+## 1.21.6
+
+* Require Dart >= 2.18.0
+* Fix the coverage usage example in the README.md
+* Support the latest `package:test_api` and `package:test_core`.
+
+## 1.21.5
+
+* Fix `printOnFailure` output to be associated with the correct test.
+* Migrate all dom interactions to static interop.
+
+## 1.21.4
+
+* Make the labels for test loading more readable in the compact and expanded
+ reporters, use gray instead of black.
+* Print a command to re-run the failed test after each failure in the compact
+ reporter.
+* Fix the package config path used when running pre-compiled vm tests.
+
+## 1.21.3
+
+* Support the latest `package:test_api` and `package:test_core`.
+
+## 1.21.2
+
+* Add `Target` to restrict `TestOn` annotation to library level.
+* Update the github reporter to output the platform in the test names when
+ multiple platforms are used.
+* Fix `spawnHybridUri` support for `package:` uris.
+
+## 1.21.1
+
+* Fix a bug loading JS sources with non-utf8 content while parsing coverage
+ information from chrome.
+
+## 1.21.0
+
+* Allow analyzer version `4.x`.
+* Add a `github` reporter option for use with GitHub Actions.
+* Make the `github` test reporter the default when we detect we're running on
+ GitHub Actions.
+
+## 1.20.2
+
+* Drop `dart2js-path` command line argument.
+* Allow loading tests under a path with the directory named `packages`.
+* Add retry for launching browsers. Reduce timeout back to 30 seconds.
+
+## 1.20.1
+
+* Allow the latest `vm_service` package.
+
+## 1.20.0
+
+* Update `analyzer` constraint to `>=2.0.0 <4.0.0`.
+* Add an `--ignore-timeouts` command line flag, which disables all timeouts
+ for all tests. This can be useful when debugging, so tests don't time out
+ during debug sessions.
+* Create a trusted types policy when available for assigning the script URL for
+ web tests.
+
+## 1.19.5
+
+* Try to get more logging from `chrome` on windows to diagnose intermittent
+ failures.
+
+## 1.19.4
+
+* Wait for paused VM platform isolates before shutdown.
+* `TestFailure` implements `Exception` for compatibility with
+ `only_throw_exceptions`.
+
+## 1.19.3
+
+* Remove duplicate logging of suggestion to enable the `chain-stack-traces`
+ flag, a single log will now appear at the end.
+
+## 1.19.2
+
+* Republish with missing JS file for browser tests.
+
+## 1.19.1
+
+* Fix parsing of file paths into a URI on windows.
+
+## 1.19.0
+
+* Support query parameters `name`, `full-name`, `line`, and `col` on test paths,
+ which will apply the filters to only those test suites.
+ * All specified filters must match for a test to run.
+ * Global filters (ie: `--name`) are also still respected and must match.
+ * The `line` and `col` will match if any frame from the test trace matches
+ (the test trace is the current stack trace where `test` is invoked).
+* Give a better exception when using `markTestSkipped` outside of a test.
+
+## 1.18.2
+
+* Publish with the `host.dart.js` file.
+
+## 1.18.1
+
+* Add defaulting for older test backends that don't pass a configuration for
+ the `allow_duplicate_test_names` parameter to the remote listener.
+
+## 1.18.0
+
+* Add configuration to disallow duplicate test and group names. See the
+ [docs][allow_duplicate_test_names] for more information.
+* Remove dependency on pedantic.
+
+[allow_duplicate_test_names]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#allow_duplicate_test_names
+
+## 1.17.12
+
+* Support the latest `test_core`.
+* Re-use the cached dill file from previous runs on subsequent runs.
+
+## 1.17.11
+
+* Use the latest `package:matcher`.
+ * Change many argument types from `dynamic` to `Object?`.
+ * Fix `stringContainsInOrder` to account for repetitions and empty strings.
+ * **Note**: This may break some existing tests, as the behavior does change.
+
+## 1.17.10
+
+* Report incomplete tests as errors in the JSON reporter when the run is
+ canceled early.
+* Update `analyzer` constraint to `>=1.0.0 <3.0.0`.
+
+## 1.17.9
+
+* Fix a bug where a tag level configuration would cause test suites with that
+ tag to ignore the `--test-randomize-ordering-seed` argument.
+
+## 1.17.8
+
+* Update json reporter docs with updated nullability annotations and
+ descriptions.
+* Add `time` field to the json reporters `allSuites` event type so that all
+ event types can be unified.
+
+## 1.17.7
+
+* Support the latest `test_core`.
+
+## 1.17.6
+
+* Give a better error when `printOnFailure` is called from outside a test
+ zone.
+
+## 1.17.5
+
+* Support the latest vm_service release (`7.0.0`).
+
+## 1.17.4
+
+* Fix race condition between compilation of vm tests and the running of
+ isolates.
+
+## 1.17.3
+
+* Forward experiment args from the runner executable to the compiler with the
+ new vm test loading strategy.
+
+## 1.17.2
+
+* Fix a windows issue with the new loading strategy.
+
+## 1.17.1
+
+* Fix an issue where you couldn't have tests compiled in both sound and
+ unsound null safety modes.
+
+## 1.17.0
+
+* Change the default way VM tests are launched and ran to greatly speed up
+ loading performance.
+ * You can force the old strategy with `--use-data-isolate-strategy` flag if
+ you run into issues, but please also file a bug.
+* Disable stack trace chaining by default. It can be re-enabled by explicitly
+ passing the `--chain-stack-traces` flag.
+* Remove `phantomjs` support completely, it was previously broken.
+* Fix `expectAsync` function type checks.
+* Add libraries `scaffolding.dart`, and `expect.dart` to allow importing a
+ subset of the normal surface area.
+
+## 1.16.8
+
+* Fix an issue where coverage collection could hang on Chrome.
+* ~~Disable stack trace chaining by default. It can be re-enabled by explicitly
+ passing the `--chain-stack-traces` flag.~~
+
+## 1.16.7
+
+* Update `spawnHybridCode` to default to the current packages language version.
+* Update `test_core` and `test_api` deps.
+
+## 1.16.6
+
+* Complete the migration to null safety.
+
+## 1.16.5
+
+* Expand several deps to allow the latest versions.
+
+## 1.16.4
+
+* Update `test_core` dependency to `0.3.14`.
+
+## 1.16.3
+
+* Update `web_socket_channel` dependency to support latest.
+
+## 1.16.2
+
+* Update `test_core` dependency to `0.3.13`.
+
+## 1.16.1
+
+* Allow the latest analyzer `1.0.0`.
+
+## 1.16.0
+
+* Stable null safety release.
+
+## 1.16.0-nullsafety.19
+
+* Use the `test_api` for stable null safety.
+
+## 1.16.0-nullsafety.18
+
+* Expand upper bound constraints for some null safe migrated packages.
+
+## 1.16.0-nullsafety.17
+
+* Support the latest shelf release (`1.x.x`).
+
+## 1.16.0-nullsafety.16
+
+* Support the latest vm_service release (`6.x.x`).
+
+## 1.16.0-nullsafety.15
+
+* Support the latest coverage release (`0.15.x`).
+
+## 1.16.0-nullsafety.14
+
+* Allow the latest args release (`2.x`).
+
+## 1.16.0-nullsafety.13
+
+* Allow the latest glob release (`2.x`).
+
+## 1.16.0-nullsafety.12
+
+* Fix `spawnHybridUri` on windows.
+* Fix failures running tests on the `node` platform.
+* Allow `package:yaml` version `3.x.x`.
+
+## 1.16.0-nullsafety.11
+
+* Set up a stack trace mapper in precompiled mode if source maps exist. If
+ the stack traces are already mapped then this has no effect, otherwise it
+ will try to map any JS lines it sees.
+
+## 1.16.0-nullsafety.10
+
+* Allow injecting a test channel for browser tests.
+* Allow `package:analyzer` version `0.41.x`.
+
+## 1.16.0-nullsafety.9
+
+* Fix `spawnHybridUri` to respect language versioning of the spawned uri.
+
+## 1.16.0-nullsafety.8
+
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 1.16.0-nullsafety.7
+
+* Allow prerelease versions of the 2.12 sdk.
+
+## 1.16.0-nullsafety.6
+
+* Add `markTestSkipped` API.
+
+## 1.16.0-nullsafety.5
+
+* Allow `2.10` stable and `2.11.0-dev` SDKs.
+* Annotate the classes used as annotations to restrict their usage to library
+ level.
+* Stop required a `SILENT_OBSERVATORY` environment variable to run with
+ debugging and the JSON reporter.
+
+## 1.16.0-nullsafety.4
+
+* Depend on the latest test_core.
+
+## 1.16.0-nullsafety.3
+
+* Clean up `--help` output.
+
+## 1.16.0-nullsafety.2
+
+* Allow version `0.40.x` of `analyzer`.
+
+## 1.16.0-nullsafety.1
+
+* Depend on the latest test_core.
+
+## 1.16.0-nullsafety
+
+* Support running tests with null safety.
+ * Note that the test runner itself is not fully migrated yet.
+* Add the `Fake` class, available through `package:test_api/fake.dart`. This
+ was previously part of the Mockito package, but with null safety it is useful
+ enough that we decided to make it available through `package:test`. In a
+ future release it will be made available directly through
+ `package:test_api/test_api.dart` (and hence through
+ `package:test_core/test_core.dart` and `package:test/test.dart`).
+
+## 1.15.7 (Backport)
+
+* Fix `spawnHybridUri` on windows.
+
+## 1.15.6 (Backport)
+
+* Support `package:analyzer` version `0.41.x`.
+
+## 1.15.5 (Backport)
+
+* Fix `spawnHybridUri` to respect language versioning of the spawned uri.
+
+## 1.15.4
+
+* Allow analyzer 0.40.x.
+
+## 1.15.3
+
+* Update to `matcher` version `0.12.9` which improves the mismatch description
+ for deep collection equality matchers and TypeMatcher.
+
+## 1.15.2
+
+* Use the latest `test_core` which resolves an issue with the latest
+ `package:meta`.
+
+## 1.15.1
+
+* Avoid a confusing stack trace when there is a problem loading a platform when
+ using the JSON reporter and enabling debugging.
+* Restore behavior of listening for both `IPv6` and `IPv4` sockets for the node
+ platform.
+
+## 1.15.0
+
+* Update bootstrapping logic to ensure the bootstrap library has
+ the same language version as the test.
+* The Node platform will now communicate over only IPv6 if it is available.
+
+## 1.14.7
+
+* Support the latest `package:coverage`.
+
+
+## 1.14.6
+
+* Update `test_core` to `0.3.6`.
+
+## 1.14.5
+
+* Add additional information to an exception when we end up with a null
+ `RunnerSuite`.
+
+## 1.14.4
+
+* Use non-headless Chrome when provided the flag `--pause-after-load`.
+
+## 1.14.3
+
+* Fix an issue where coverage tests could not run in Chrome headless.
+* Fix an issue where coverage collection would not work with source
+ maps that contained absolute file URIs.
+* Fix error messages for incorrect string literals in test annotations.
+* Update `test_core` to `0.3.4`.
+
+## 1.14.2
+
+* Update `test_core` to `0.3.3`.
+
+## 1.14.1
+
+* Allow the latest shelf_packages_handler.
+
+## 1.14.0
+
+* Drop the `package_resolver` dependency for the `package_config` dependency
+ which is lower level.
+
+## 1.13.0
+
+* Enable asserts in code running through `spawnHybrid` APIs.
+* Exit with a non-zero code if no tests were ran, whether due to skips or having
+ no tests defined.
+* Fix the stack trace labels in SDK code for `dart2js` compiled tests.
+* Cancel any StreamQueue that is created as a part of a stream matcher once it
+ is done matching.
+ * This fixes a bug where using a matcher on a custom stream controller and
+ then awaiting the `close()` method on that controller would hang.
+* Avoid causing the test runner to hang if there is a timeout during a
+ `tearDown` callback following a failing test case.
+
+## 1.12.0
+
+* Bump minimum SDK to `2.4.0` for safer usage of for-loop elements.
+* Deprecate `PhantomJS` and provide warning when used. Support for `PhantomJS`
+ will be removed in version `2.0.0`.
+* Support coverage collection for the Chrome platform. See `README.md` for usage
+ details.
+
+## 1.11.1
+
+* Allow `test_api` `0.2.13` to work around a bug in the SDK version `2.3.0`.
+
+## 1.11.0
+
+* Add `file_reporters` configuration option and `--file-reporter` CLI option to
+ allow specifying a separate reporter that writes to a file instead of stdout.
+
+## 1.10.0
+
+* Add `customHtmlTemplateFile` configuration option to allow sharing an
+ html template between tests
+* Depend on the latest `package:test_core`.
+* Depend on the latest `package:test_api`.
+
+## 1.9.4
+
+* Extend the timeout for synthetic tests, e.g. `tearDownAll`.
+* Depend on the latest `package:test_core`.
+* Depend on the latest `package:test_api`.
+
+## 1.9.3
+
+* Depend on the latest `package:test_core`.
+* Support the latest `package:analyzer`.
+* Update to latest `package:matcher`. Improves output for instances of private
+ classes.
+
+## 1.9.2
+
+* Depend on the latest `package:test_api` and `package:test_core`.
+* While using `solo` tests that are not run will now be reported as skipped.
+
+## 1.9.1
+
+* Depend on latest `test_core`.
+
+## 1.9.0
+
+* Implement code coverage collection for VM based tests
+
+## 1.8.0
+
+* Expose the previously hidden sharding arguments
+ * `--total-shards` specifies how many shards the suite should
+ be split into
+ * `--shard-index` specifies which shard should be run
+
+## 1.7.0
+
+* Add a `--debug` flag for running the VM/Chrome in debug mode.
+
+## 1.6.11
+
+* Depend on the latest `test_core` and `test_api`.
+
+## 1.6.10
+
+* Depend on the latest `test_core`.
+
+## 1.6.9
+
+* Add `--disable-dev-shm-usage` to the default Chrome flags.
+
+## 1.6.8
+
+* Depend on the latest `test_core` and `test_api`.
+
+## 1.6.7
+
+* Allow `analyzer` version `0.38.x`.
+
+## 1.6.6
+
+* Pass `--server-mode` to dart2js instead of `--categories=Server` to fix a
+ warning about the flag deprecation.
+* Drop dependency on `pub_semver`.
+* Fix issue with the latest `Utf8Decoder` and the `node` platform.
+
+## 1.6.5
+
+* Depend on the latest `test_core`.
+* Depend on the latest `package:analyzer`.
+
+## 1.6.4
+
+* Don't swallow exceptions from callbacks in `expectAsync*`.
+* Internal cleanup - fix lints.
+
+## 1.6.3
+
+* Depend on latest `package:test_core`.
+ * This fixes an issue where non-completed tests were considered passing.
+
+## 1.6.2
+
+* Avoid `dart:isolate` imports on code loaded in tests.
+
+## 1.6.1
+
+* Allow `stream_channel` version `2.0.0`.
+
+## 1.6.0
+
+* Allow `analyzer` version `0.36.x`.
+* Matcher changes:
+ * Add `isA()` to create `TypeMatcher` instances in a more fluent way.
+ * Add `isCastError`.
+ * **Potentially breaking bug fix**. Ordering matchers no longer treat objects
+ with a partial ordering (such as NaN for double values) as if they had a
+ complete ordering. For instance `greaterThan` now compares with the `>`
+ operator rather not `<` and not `=`. This could cause tests which relied on
+ this bug to start failing.
+
+## 1.5.3
+
+* Allow `analyzer` version `0.35.x`.
+
+## 1.5.2
+
+* Require Dart SDK `>=2.1.0`.
+* Depend on latest `test_core` and `test_api`.
+
+## 1.5.1
+
+* Depend on latest `test_core` and `test_api`.
+
+## 1.5.0
+
+* Depend on `package:test_core` for core functionality.
+
+## 1.4.0
+
+* Depend on `package:test_api` for core functionality.
+
+## 1.3.4
+
+* Allow remote_listener to be closed and sent an event on close.
+
+## 1.3.3
+
+* Add conditional imports so that `dart:io` is not imported from the main
+ `test.dart` entrypoint unless it is available.
+* Fix an issue with dartdevc in precompiled mode and the json reporter.
+* Fix an issue parsing test metadata annotations without explicit `const`.
+
+## 1.3.2
+
+* Widen the constraints on the analyzer package.
+
+## 1.3.1
+
+* Handle parsing annotations which omit `const` on collection literals.
+* Fix an issue where `root_line`, `root_column`, and `root_url` in the
+ JSON reported may not be populated correctly on Windows.
+* Removed requirement for the test/pub_serve transformer in --pub-serve mode.
+
+## 1.3.0
+
+* When using `--precompiled`, the test runner now allows symlinks to reach
+ outside the precompiled directory. This allows more efficient creation of
+ precompiled directories (using symlinks instead of copies).
+* Updated max sdk range to `<3.0.0`.
+
+## 1.2.0
+
+* Added support for using precompiled kernel files when running vm tests.
+ * When using the `--precompiled` flag we will now first check for a
+ `<original-test-path>.vm_test.vm.app.dill` file, and if present load that
+ directly in the isolate. Otherwise the `<original-test-path>.vm_test.dart`
+ file will be used.
+
+## 1.1.0
+
+* Added a new `pid` field to the StartEvent in the json runner containing the
+ pid of the VM process running the tests.
+
+## 1.0.0
+
+* No change from `0.12.42`. We are simply signalling to users that this is a
+ well supported package and is the preferred way to write Dart tests.
+
+## 0.12.42
+
+* Add support for `solo` test and group. When the argument is `true` only tests
+ and groups marked as solo will be run. It is still recommended that users
+ instead filter their tests by using the runner argument `-n`.
+
+* Updated exported `package:matcher` to `0.12.3` which includes these updates:
+
+ - Many improvements to `TypeMatcher`
+ - Can now be used directly as `const TypeMatcher<MyType>()`.
+ - Added a type parameter to specify the target `Type`.
+ - Made the `name` constructor parameter optional and marked it deprecated.
+ It's redundant to the type parameter.
+ - Migrated all `isType` matchers to `TypeMatcher`.
+ - Added a `having` function that allows chained validations of specific
+ features of the target type.
+
+ ```dart
+ /// Validates that the object is a [RangeError] with a message containing
+ /// the string 'details' and `start` and `end` properties that are `null`.
+ final _rangeMatcher = isRangeError
+ .having((e) => e.message, 'message', contains('details'))
+ .having((e) => e.start, 'start', isNull)
+ .having((e) => e.end, 'end', isNull);
+ ```
+
+ - Deprecated the `isInstanceOf` class. Use `TypeMatcher` instead.
+
+ - Improved the output of `Matcher` instances that fail due to type errors.
+
+## 0.12.41
+
+* Add support for debugging VM tests.
+* Tweak default reporter and color logic again so that they are always enabled
+ on all non-windows platforms.
+
+## 0.12.40
+
+* Added some new optional fields to the json reporter, `root_line`,
+ `root_column`, and `root_url`. These will be present if `url` is not the same
+ as the suite url, and will represent the location in the original test suite
+ from which the call to `test` originated.
+
+## 0.12.39
+
+* Change the default reporter and color defaults to be based on
+ `stdout.supportsAnsiEscapes` instead of based on platform (previously both
+ were disabled on windows).
+
+## 0.12.38+3
+
+* Fix Dart 2 runtime errors around communicating with browsers.
+
+
+## 0.12.38+2
+
+* Fix more Dart 2 runtime type errors.
+
+## 0.12.38+1
+
+* Fix several Dart 2 runtime type errors.
+
+## 0.12.38
+
+* Give `neverCalled` a type that works in Dart 2 semantics.
+* Support `package:analyzer` `0.32.0`.
+
+## 0.12.37
+
+* Removed the transformer, and the `pub_serve.dart` entrypoint. This is not
+ being treated as a breaking change because the minimum sdk constraint now
+ points to an sdk which does not support pub serve or barback any more anyways.
+* Drop the dependency on `barback`.
+
+## 0.12.36
+
+* Expose the test bootstrapping methods, so that build systems can precompile
+ tests without relying on internal apis.
+
+## 0.12.35
+
+* Dropped support for Dart 1. Going forward only Dart 2 will be supported.
+ * If you experience blocking issues and are still on the Dart 1 sdk, we will
+ consider bug fixes on a per-case basis based on severity and impact.
+ * Drop support for `dartium` and `content-shell` platforms since those are
+ removed from the Dart 2 SDK.
+* Fixed an issue `--precompiled` node tests in subdirectories.
+* Fixed some dart2 issues with node test bootstrapping code so that dartdevc
+ tests can run.
+* Fixed default custom html handler so it correctly includes the
+ packages/test/dart.js file. This allows you to get proper errors instead of
+ timeouts if there are load exceptions in the browser.
+* Upgrade to package:matcher 0.12.2
+
+## 0.12.34
+
+* Requires at least Dart 1.24.0.
+* The `--precompiled` flag is now supported for the vm platform and the node
+ platform.
+* On browser platforms the `--precompiled` flag now serves all sources directly
+ from the precompiled directory, and will never attempt to do its own
+ compilation.
+
+## 0.12.33
+
+* Pass `--categories=Server` to `dart2js` when compiling tests for Node.js. This
+ tells it that `dart:html` is unavailable.
+
+* Don't crash when attempting to format stack traces when running via
+ `dart path/to/test.dart`.
+
+## 0.12.32+2
+
+* Work around an SDK bug that caused timeouts in asynchronous code.
+
+## 0.12.32+1
+
+* Fix a bug that broke content shell on Dart 1.24.
+
+## 0.12.32
+
+* Add an `include` configuration field which specifies the path to another
+ configuration file whose configuration should be used.
+
+* Add a `google` platform selector variable that's only true on Google's
+ internal infrastructure.
+
+## 0.12.31
+
+* Add a `headless` configuration option for Chrome.
+
+* Re-enable headless mode for Chrome by default.
+
+* Don't hang when a Node.js test fails to compile.
+
+## 0.12.30+4
+
+* Stop running Chrome in headless mode temporarily to work around a browser bug.
+
+## 0.12.30+3
+
+* Fix a memory leak when loading browser tests.
+
+## 0.12.30+2
+
+* Avoid loading test suites whose tags are excluded by `--excluded-tags`.
+
+## 0.12.30+1
+
+* Internal changes.
+
+## 0.12.30
+
+* Platform selectors for operating systems now work for Node.js tests
+ ([#742][]).
+
+* `fail()` is now typed to return `Null`, so it can be used in the same places
+ as a raw `throw`.
+
+* Run Chrome in headless mode unless debugging is enabled.
+
+[#742]: https://github.com/dart-lang/test/issues/742
+
+## 0.12.29+1
+
+* Fix strong mode runtime cast failures.
+
+## 0.12.29
+
+* Node.js tests can now import modules from a top-level `node_modules`
+ directory, if one exists.
+
+* Raw `console.log()` calls no longer crash Node.js tests.
+
+* When a browser crashes, include its standard output in the error message.
+
+## 0.12.28+1
+
+* Add a `pumpEventQueue()` function to make it easy to wait until all
+ asynchronous tasks are complete.
+
+* Add a `neverCalled` getter that returns a function that causes the test to
+ fail if it's ever called.
+
+## 0.12.27+1
+
+* Increase the timeout for loading tests to 12 minutes.
+
+## 0.12.27
+
+* When `addTearDown()` is called within a call to `setUpAll()`, it runs its
+ callback after *all* tests instead of running it after the `setUpAll()`
+ callback.
+
+* When running in an interactive terminal, the test runner now prints status
+ lines as wide as the terminal and no wider.
+
+## 0.12.26+1
+
+* Fix lower bound on package `stack_trace`. Now 1.6.0.
+* Manually close browser process streams to prevent test hangs.
+
+## 0.12.26
+
+* The `spawnHybridUri()` function now allows root-relative URLs, which are
+ interpreted as relative to the root of the package.
+
+## 0.12.25
+
+* Add a `override_platforms` configuration field which allows test platforms'
+ settings (such as browsers' executables) to be overridden by the user.
+
+* Add a `define_platforms` configuration field which makes it possible to define
+ new platforms that use the same logic as existing ones but have different
+ settings.
+
+## 0.12.24+8
+
+* `spawnHybridUri()` now interprets relative URIs correctly in browser tests.
+
+## 0.12.24+7
+
+* Declare support for `async` 2.0.0.
+
+## 0.12.24+6
+
+* Small refactoring to make the package compatible with strong-mode compliant Zone API.
+ No user-visible change.
+
+## 0.12.24+5
+
+* Expose a way for tests to forward a `loadException` to the server.
+
+## 0.12.24+4
+
+* Drain browser process `stdout` and `stdin`. This resolves test flakiness, especially in Travis
+ with the `Precise` image.
+
+## 0.12.24+3
+
+* Extend `deserializeTimeout`.
+
+## 0.12.24+2
+
+* Only force exit if `FORCE_TEST_EXIT` is set in the environment.
+
+## 0.12.24+1
+
+* Widen version constraint on `analyzer`.
+
+## 0.12.24
+
+* Add a `node` platform for compiling tests to JavaScript and running them on
+ Node.js.
+
+## 0.12.23+1
+
+* Remove unused imports.
+
+## 0.12.23
+
+* Add a `fold_stack_frames` field for `dart_test.yaml`. This will
+ allow users to customize which packages' frames are folded.
+
+## 0.12.22+2
+
+* Properly allocate ports when debugging Chrome and Dartium in an IPv6-only
+ environment.
+
+## 0.12.22+1
+
+* Support `args` 1.0.0.
+
+* Run tear-down callbacks in the same error zone as the test function. This
+ makes it possible to safely share `Future`s and `Stream`s between tests and
+ their tear-downs.
+
+## 0.12.22
+
+* Add a `retry` option to `test()` and `group()` functions, as well
+ as `@Retry()` annotation for test files and a `retry`
+ configuration field for `dart_test.yaml`. A test with reties
+ enabled will be re-run if it fails for a reason other than a
+ `TestFailure`.
+
+* Add a `--no-retry` runner flag that disables retries of failing tests.
+
+* Fix a "concurrent modification during iteration" error when calling
+ `addTearDown()` from within a tear down.
+
+## 0.12.21
+
+* Add a `doesNotComplete` matcher that asserts that a Future never completes.
+
+* `throwsA()` and all related matchers will now match functions that return
+ `Future`s that emit exceptions.
+
+* Respect `onPlatform` for groups.
+
+* Only print browser load errors once per browser.
+
+* Gracefully time out when attempting to deserialize a test suite.
+
+## 0.12.20+13
+
+* Upgrade to package:matcher 0.12.1
+
+## 0.12.20+12
+
+* Now support `v0.30.0` of `pkg/analyzer`
+
+* The test executable now does a "hard exit" when complete to ensure lingering
+ isolates or async code don't block completion. This may affect users trying
+ to use the Dart service protocol or observatory.
+
+## 0.12.20+11
+
+* Refactor bootstrapping to simplify the test/pub_serve transformer.
+
+## 0.12.20+10
+
+* Refactor for internal tools.
+
+## 0.12.20+9
+
+* Introduce new flag `--chain-stack-traces` to conditionally chain stack traces.
+
+## 0.12.20+8
+
+* Fixed more blockers for compiling with `dev_compiler`.
+* Dartfmt the entire repo.
+
+* **Note:** 0.12.20+5-0.12.20+7 were tagged but not officially published.
+
+## 0.12.20+4
+
+* Fixed strong-mode errors and other blockers for compiling with `dev_compiler`.
+
+## 0.12.20+3
+
+* `--pause-after-load` no longer deadlocks with recent versions of Chrome.
+
+* Fix Dartified stack traces for JS-compiled tests run through `pub serve`.
+
+## 0.12.20+2
+
+* Print "[E]" after test failures to make them easier to identify visually and
+ via automated search.
+
+## 0.12.20+1
+
+* Tighten the dependency on `stream_channel` to reflect the APIs being used.
+
+* Use a 1024 x 768 iframe for browser tests.
+
+## 0.12.20
+
+* **Breaking change:** The `expect()` method no longer returns a `Future`, since
+ this broke backwards-compatibility in cases where a void function was
+ returning an `expect()` (such as `void foo() => expect(...)`). Instead, a new
+ `expectLater()` function has been added that return a `Future` that completes
+ when the matcher has finished running.
+
+* The `verbose` parameter to `expect()` and the `formatFailure()` function are
+ deprecated.
+
+## 0.12.19+1
+
+* Make sure asynchronous matchers that can fail synchronously, such as
+ `throws*()` and `prints()`, can be used with synchronous matcher operators
+ like `isNot()`.
+
+## 0.12.19
+
+* Added the `StreamMatcher` class, as well as several built-in stream matchers:
+ `emits()`, `emitsError()`, `emitsDone, mayEmit()`, `mayEmitMultiple()`,
+ `emitsAnyOf()`, `emitsInOrder()`, `emitsInAnyOrder()`, and `neverEmits()`.
+
+* `expect()` now returns a Future for the asynchronous matchers `completes`,
+ `completion()`, `throws*()`, and `prints()`.
+
+* Add a `printOnFailure()` method for providing debugging information that's
+ only printed when a test fails.
+
+* Automatically configure the [`term_glyph`][term_glyph] package to use ASCII
+ glyphs when the test runner is running on Windows.
+
+[term_glyph]: https://pub.dev/packages/term_glyph
+
+* Deprecate the `throws` matcher in favor of `throwsA()`.
+
+* Deprecate the `Throws` class. These matchers should only be constructed via
+ `throwsA()`.
+
+## 0.12.18+1
+
+* Fix the deprecated `expectAsync()` function. The deprecation caused it to
+ fail to support functions that take arguments.
+
+## 0.12.18
+
+* Add an `addTearDown()` function, which allows tests to register additional
+ tear-down callbacks as they're running.
+
+* Add the `spawnHybridUri()` and `spawnHybridCode()` functions, which allow
+ browser tests to run code on the VM.
+
+* Fix the new `expectAsync` functions so that they don't produce analysis errors
+ when passed callbacks with optional arguments.
+
+## 0.12.17+3
+
+* Internal changes only.
+
+## 0.12.17+2
+
+* Fix Dartium debugging on Windows.
+
+## 0.12.17+1
+
+* Fix a bug where tags couldn't be marked as skipped.
+
+## 0.12.17
+
+* Deprecate `expectAsync` and `expectAsyncUntil`, since they currently can't be
+ made to work cleanly in strong mode. They are replaced with separate methods
+ for each number of callback arguments:
+ * `expectAsync0`, `expectAsync1`, ... `expectAsync6`, and
+ * `expectAsyncUntil0`, `expectAsyncUntil1`, ... `expectAsyncUntil6`.
+
+## 0.12.16
+
+* Allow tools to interact with browser debuggers using the JSON reporter.
+
+## 0.12.15+12
+
+* Fix a race condition that could cause the runner to stall for up to three
+ seconds after completing.
+
+## 0.12.15+11
+
+* Make test iframes visible when debugging.
+
+## 0.12.15+10
+
+* Throw a better error if a group body is asynchronous.
+
+## 0.12.15+9
+
+* Widen version constraint on `analyzer`.
+
+## 0.12.15+8
+
+* Make test suites with thousands of tests load much faster on the VM (and
+ possibly other platforms).
+
+## 0.12.15+7
+
+* Fix a bug where tags would be dropped when `on_platform` was defined in a
+ config file.
+
+## 0.12.15+6
+
+* Fix a broken link in the `--help` documentation.
+
+## 0.12.15+5
+
+* Internal-only change.
+
+## 0.12.15+4
+
+* Widen version constraint on `analyzer`.
+
+## 0.12.15+3
+
+* Move `nestingMiddleware` to `lib/src/util/path_handler.dart` to enable a
+ cleaner separation between test-runner files and test writing files.
+
+## 0.12.15+2
+
+* Support running without a `packages/` directory.
+
+## 0.12.15+1
+
+* Declare support for version 1.19 of the Dart SDK.
+
+## 0.12.15
+
+* Add a `skip` parameter to `expect()`. Marking a single expect as skipped will
+ cause the test itself to be marked as skipped.
+
+* Add a `--run-skipped` parameter and `run_skipped` configuration field that
+ cause tests to be run even if they're marked as skipped.
+
+## 0.12.14+1
+
+* Narrow the constraint on `yaml`.
+
+## 0.12.14
+
+* Add test and group location information to the JSON reporter.
+
+## 0.12.13+5
+
+* Declare support for version 1.18 of the Dart SDK.
+
+* Use the latest `collection` package.
+
+## 0.12.13+4
+
+* Compatibility with an upcoming release of the `collection` package.
+
+## 0.12.13+3
+
+* Internal changes only.
+
+## 0.12.13+2
+
+* Fix all strong-mode errors and warnings.
+
+## 0.12.13+1
+
+* Declare support for version 1.17 of the Dart SDK.
+
+## 0.12.13
+
+* Add support for a global configuration file. On Windows, this file defaults to
+ `%LOCALAPPDATA%\DartTest.yaml`. On Unix, it defaults to `~/.dart_test.yaml`.
+ It can also be explicitly set using the `DART_TEST_CONFIG` environment
+ variable. See [the configuration documentation][global config] for details.
+
+* The `--name` and `--plain-name` arguments may be passed more than once, and
+ may be passed together. A test must match all name constraints in order to be
+ run.
+
+* Add `names` and `plain_names` fields to the package configuration file. These
+ allow presets to control which tests are run based on their names.
+
+* Add `include_tags` and `exclude_tags` fields to the package configuration
+ file. These allow presets to control which tests are run based on their tags.
+
+* Add a `pause_after_load` field to the package configuration file. This allows
+ presets to enable debugging mode.
+
+[global config]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#global-configuration
+
+## 0.12.12
+
+* Add support for [test presets][]. These are defined using the `presets` field
+ in the package configuration file. They can be selected by passing `--preset`
+ or `-P`, or by using the `add_presets` field in the package configuration
+ file.
+
+* Add an `on_os` field to the package configuration file that allows users to
+ select different configuration for different operating systems.
+
+* Add an `on_platform` field to the package configuration file that allows users
+ to configure all tests differently depending on which platform they run on.
+
+* Add an `ios` platform selector variable. This variable will only be true when
+ the `test` executable itself is running on iOS, not when it's running browser
+ tests on an iOS browser.
+
+[test presets]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/package_config.md#configuration-presets
+
+## 0.12.11+2
+
+* Update to `shelf_web_socket` 0.2.0.
+
+## 0.12.11+1
+
+* Purely internal change.
+
+## 0.12.11
+
+* Add a `tags` field to the package configuration file that allows users to
+ provide configuration for specific tags.
+
+* The `--tags` and `--exclude-tags` command-line flags now allow
+ [boolean selector syntax][]. For example, you can now pass `--tags "(chrome ||
+ firefox) && !slow"` to select quick Chrome or Firefox tests.
+
+[boolean selector syntax]: https://github.com/dart-lang/boolean_selector/blob/master/README.md
+
+## 0.12.10+2
+
+* Re-add help output separators.
+
+* Tighten the constraint on `args`.
+
+## 0.12.10+1
+
+* Temporarily remove separators from the help output. Version 0.12.8 was
+ erroneously released without an appropriate `args` constraint for the features
+ it used; this version will help ensure that users who can't use `args` 0.13.1
+ will get a working version of `test`.
+
+## 0.12.10
+
+* Add support for a package-level configuration file called `dart_test.yaml`.
+
+## 0.12.9
+
+* Add `SuiteEvent` to the JSON reporter, which reports data about the suites in
+ which tests are run.
+
+* Add `AllSuitesEvent` to the JSON reporter, which reports the total number of
+ suites that will be run.
+
+* Add `Group.testCount` to the JSON reporter, which reports the total number of
+ tests in each group.
+
+## 0.12.8
+
+* Organize the `--help` output into sections.
+
+* Add a `--timeout` flag.
+
+## 0.12.7
+
+* Add the ability to re-run tests while debugging. When the browser is paused at
+ a breakpoint, the test runner will open an interactive console on the command
+ line that can be used to restart the test.
+
+* Add support for passing any object as a description to `test()` and `group()`.
+ These objects will be converted to strings.
+
+* Add the ability to tag tests. Tests with specific tags may be run by passing
+ the `--tags` command-line argument, or excluded by passing the
+ `--exclude-tags` parameter.
+
+ This feature is not yet complete. For now, tags are only intended to be added
+ temporarily to enable use-cases like [focusing][] on a specific test or group.
+ Further development can be followed on [the issue tracker][issue 16].
+
+* Wait for a test's tear-down logic to run, even if it times out.
+
+[focusing]: https://jasmine.github.io/2.1/focused_specs.html
+[issue 16]: https://github.com/dart-lang/test/issues/16
+
+## 0.12.6+2
+
+* Declare compatibility with `http_parser` 2.0.0.
+
+## 0.12.6+1
+
+* Declare compatibility with `http_multi_server` 2.0.0.
+
+## 0.12.6
+
+* Add a machine-readable JSON reporter. For details, see
+ [the protocol documentation][json-protocol].
+
+* Skipped groups now properly print skip messages.
+
+[json-protocol]: https://github.com/dart-lang/test/blob/master/pkgs/test/json_reporter.md
+
+## 0.12.5+2
+
+* Declare compatibility with Dart 1.14 and 1.15.
+
+## 0.12.5+1
+
+* Fixed a deadlock bug when using `setUpAll()` and `tearDownAll()`.
+
+## 0.12.5
+
+* Add `setUpAll()` and `tearDownAll()` methods that run callbacks before and
+ after all tests in a group or suite. **Note that these methods are for special
+ cases and should be avoided**—they make it very easy to accidentally introduce
+ dependencies between tests. Use `setUp()` and `tearDown()` instead if
+ possible.
+
+* Allow `setUp()` and `tearDown()` to be called multiple times within the same
+ group.
+
+* When a `tearDown()` callback runs after a signal has been caught, it can now
+ schedule out-of-band asynchronous callbacks normally rather than having them
+ throw exceptions.
+
+* Don't show package warnings when compiling tests with dart2js. This was
+ accidentally enabled in 0.12.2, but was never intended.
+
+## 0.12.4+9
+
+* If a `tearDown()` callback throws an error, outer `tearDown()` callbacks are
+ still executed.
+
+## 0.12.4+8
+
+* Don't compile tests to JavaScript when running via `pub serve` on Dartium or
+ content shell.
+
+## 0.12.4+7
+
+* Support `http_parser` 1.0.0.
+
+## 0.12.4+6
+
+* Fix a broken link in the README.
+
+## 0.12.4+5
+
+* Internal changes only.
+
+## 0.12.4+4
+
+* Widen the Dart SDK constraint to include `1.13.0`.
+
+## 0.12.4+3
+
+* Make source maps work properly in the browser when not using `--pub-serve`.
+
+## 0.12.4+2
+
+* Fix a memory leak when running many browser tests where old test suites failed
+ to be unloaded when they were supposed to.
+
+## 0.12.4+1
+
+* Require Dart SDK >= `1.11.0` and `shelf` >= `0.6.0`, allowing `test` to remove
+ various hacks and workarounds.
+
+## 0.12.4
+
+* Add a `--pause-after-load` flag that pauses the test runner after each suite
+ is loaded so that breakpoints and other debugging annotations can be added.
+ Currently this is only supported on browsers.
+
+* Add a `Timeout.none` value indicating that a test should never time out.
+
+* The `dart-vm` platform selector variable is now `true` for Dartium and content
+ shell.
+
+* The compact reporter no longer prints status lines that only update the clock
+ if they would get in the way of messages or errors from a test.
+
+* The expanded reporter no longer double-prints the descriptions of skipped
+ tests.
+
+## 0.12.3+9
+
+* Widen the constraint on `analyzer` to include `0.26.0`.
+
+## 0.12.3+8
+
+* Fix an uncaught error that could crop up when killing the test runner process
+ at the wrong time.
+
+## 0.12.3+7
+
+* Add a missing dependency on the `collection` package.
+
+## 0.12.3+6
+
+**This version was unpublished due to [issue 287][].**
+
+* Properly report load errors caused by failing to start browsers.
+
+* Substantially increase browser timeouts. These timeouts are the cause of a lot
+ of flakiness, and now that they don't block test running there's less harm in
+ making them longer.
+
+## 0.12.3+5
+
+**This version was unpublished due to [issue 287][].**
+
+* Fix a crash when skipping tests because their platforms don't match.
+
+## 0.12.3+4
+
+**This version was unpublished due to [issue 287][].**
+
+* The compact reporter will update the timer every second, rather than only
+ updating it occasionally.
+
+* The compact reporter will now print the full, untruncated test name before any
+ errors or prints emitted by a test.
+
+* The expanded reporter will now *always* print the full, untruncated test name.
+
+## 0.12.3+3
+
+**This version was unpublished due to [issue 287][].**
+
+* Limit the number of test suites loaded at once. This helps ensure that the
+ test runner won't run out of memory when running many test suites that each
+ load a large amount of code.
+
+## 0.12.3+2
+
+**This version was unpublished due to [issue 287][].**
+
+[issue 287]: https://github.com/dart-lang/test/issues/287
+
+* Improve the display of syntax errors in VM tests.
+
+* Work around a [Firefox bug][]. Computed styles now work in tests on Firefox.
+
+[Firefox bug]: https://bugzilla.mozilla.org/show_bug.cgi?id=548397
+
+* Fix a bug where VM tests would be loaded from the wrong URLs on Windows (or in
+ special circumstances on other operating systems).
+
+## 0.12.3+1
+
+* Fix a bug that caused the test runner to crash on Windows because symlink
+ resolution failed.
+
+## 0.12.3
+
+* If a future matched against the `completes` or `completion()` matcher throws
+ an error, that error is printed directly rather than being wrapped in a
+ string. This allows such errors to be captured using the Zone API and improves
+ formatting.
+
+* Improve support for Polymer tests. This fixes a flaky time-out error and adds
+ support for Dartifying JavaScript stack traces when running Polymer tests via
+ `pub serve`.
+
+* In order to be more extensible, all exception handling within tests now uses
+ the Zone API.
+
+* Add a heartbeat to reset a test's timeout whenever the test interacts with the
+ test infrastructure.
+
+* `expect()`, `expectAsync()`, and `expectAsyncUntil()` throw more useful errors
+ if called outside a test body.
+
+## 0.12.2
+
+* Convert JavaScript stack traces into Dart stack traces using source maps. This
+ can be disabled with the new `--js-trace` flag.
+
+* Improve the browser test suite timeout logic to avoid timeouts when running
+ many browser suites at once.
+
+## 0.12.1
+
+* Add a `--verbose-trace` flag to include core library frames in stack traces.
+
+## 0.12.0
+
+### Test Runner
+
+`0.12.0` adds support for a test runner, which can be run via
+`pub run test:test` (or `pub run test` in Dart 1.10). By default it runs all
+files recursively in the `test/` directory that end in `_test.dart` and aren't
+in a `packages/` directory.
+
+The test runner supports running tests on the Dart VM and many different
+browsers. Test files can use the `@TestOn` annotation to declare which platforms
+they support. For more information on this and many more new features, see [the
+README](README).
+
+[README]: https://github.com/dart-lang/test/blob/master/README.md
+
+### Removed and Changed APIs
+
+As part of moving to a runner-based model, most test configuration is moving out
+of the test file and into the runner. As such, many ancillary APIs have been
+removed. These APIs include `skip_` and `solo_` functions, `Configuration` and
+all its subclasses, `TestCase`, `TestFunction`, `testConfiguration`,
+`formatStacks`, `filterStacks`, `groupSep`, `logMessage`, `testCases`,
+`BREATH_INTERVAL`, `currentTestCase`, `PASS`, `FAIL`, `ERROR`, `filterTests`,
+`runTests`, `ensureInitialized`, `setSoloTest`, `enableTest`, `disableTest`, and
+`withTestEnvironment`.
+
+`FailureHandler`, `DefaultFailureHandler`, `configureExpectFailureHandler`, and
+`getOrCreateExpectFailureHandler` which used to be exported from the `matcher`
+package have also been removed. They existed to enable integration between
+`test` and `matcher` that has been streamlined.
+
+A number of APIs from `matcher` have been into `test`, including: `completes`,
+`completion`, `ErrorFormatter`, `expect`,`fail`, `prints`, `TestFailure`,
+`Throws`, and all of the `throws` methods. Some of these have changed slightly:
+
+* `expect` no longer has a named `failureHandler` argument.
+
+* `expect` added an optional `formatter` argument.
+
+* `completion` argument `id` renamed to `description`.
+
+## 0.11.6+4
+
+* Fix some strong mode warnings we missed in the `vm_config.dart` and
+ `html_config.dart` libraries.
+
+## 0.11.6+3
+
+* Fix a bug introduced in 0.11.6+2 in which operator matchers broke when taking
+ lists of matchers.
+
+## 0.11.6+2
+
+* Fix all strong mode warnings.
+
+## 0.11.6+1
+
+* Give tests more time to start running.
+
+## 0.11.6
+
+* Merge in the last `0.11.x` release of `matcher` to allow projects to use both
+ `test` and `unittest` without conflicts.
+
+* Fix running individual tests with `HtmlIndividualConfiguration` when the test
+ name contains URI-escaped values and is provided with the `group` query
+ parameter.
+
+## 0.11.5+1
+
+* Internal code cleanups and documentation improvements.
+
+## 0.11.5
+
+* Bumped the version constraint for `matcher`.
+
+## 0.11.4
+
+* Bump the version constraint for `matcher`.
+
+## 0.11.3
+
+* Narrow the constraint on matcher to ensure that new features are reflected in
+ unittest's version.
+
+## 0.11.2
+
+* Prints a warning instead of throwing an error when setting the test
+ configuration after it has already been set. The first configuration is always
+ used.
+
+## 0.11.1+1
+
+* Fix bug in withTestEnvironment where test cases were not reinitialized if
+ called multiple times.
+
+## 0.11.1
+
+* Add `reason` named argument to `expectAsync` and `expectAsyncUntil`, which has
+ the same definition as `expect`'s `reason` argument.
+* Added support for private test environments.
+
+## 0.11.0+6
+
+* Refactored package tests.
+
+## 0.11.0+5
+
+* Release test functions after each test is run.
+
+## 0.11.0+4
+
+* Fix for [20153](https://code.google.com/p/dart/issues/detail?id=20153)
+
+## 0.11.0+3
+
+* Updated maximum `matcher` version.
+
+## 0.11.0+2
+
+* Removed unused files from tests and standardized remaining test file names.
+
+## 0.11.0+1
+
+* Widen the version constraint for `stack_trace`.
+
+## 0.11.0
+
+* Deprecated methods have been removed:
+ * `expectAsync0`, `expectAsync1`, and `expectAsync2` - use `expectAsync`
+ instead
+ * `expectAsyncUntil0`, `expectAsyncUntil1`, and `expectAsyncUntil2` - use
+ `expectAsyncUntil` instead
+ * `guardAsync` - no longer needed
+ * `protectAsync0`, `protectAsync1`, and `protectAsync2` - no longer needed
+* `matcher.dart` and `mirror_matchers.dart` have been removed. They are now in
+ the `matcher` package.
+* `mock.dart` has been removed. It is now in the `mock` package.
+
+## 0.10.1+2
+
+* Fixed deprecation message for `mock`.
+
+## 0.10.1+1
+
+* Fixed CHANGELOG
+* Moved to triple-slash for all doc comments.
+
+## 0.10.1
+
+* **DEPRECATED**
+ * `matcher.dart` and `mirror_matchers.dart` are now in the `matcher`
+ package.
+ * `mock.dart` is now in the `mock` package.
+* `equals` now allows a nested matcher as an expected list element or map value
+ when doing deep matching.
+* `expectAsync` and `expectAsyncUntil` now support up to 6 positional arguments
+ and correctly handle functions with optional positional arguments with default
+ values.
+
+## 0.10.0
+
+* Each test is run in a separate `Zone`. This ensures that any exceptions that
+ occur is async operations are reported back to the source test case.
+* **DEPRECATED** `guardAsync`, `protectAsync0`, `protectAsync1`,
+ and `protectAsync2`
+ * Running each test in a `Zone` addresses the need for these methods.
+* **NEW!** `expectAsync` replaces the now deprecated `expectAsync0`,
+ `expectAsync1` and `expectAsync2`
+* **NEW!** `expectAsyncUntil` replaces the now deprecated `expectAsyncUntil0`,
+ `expectAsyncUntil1` and `expectAsyncUntil2`
+* `TestCase`:
+ * Removed properties: `setUp`, `tearDown`, `testFunction`
+ * `enabled` is now get-only
+ * Removed methods: `pass`, `fail`, `error`
+* `interactive_html_config.dart` has been removed.
+* `runTests`, `tearDown`, `setUp`, `test`, `group`, `solo_test`, and
+ `solo_group` now throw a `StateError` if called while tests are running.
+* `rerunTests` has been removed.
diff --git a/pkgs/test/LICENSE b/pkgs/test/LICENSE
new file mode 100644
index 0000000..000cd7b
--- /dev/null
+++ b/pkgs/test/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2014, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/test/README.md b/pkgs/test/README.md
new file mode 100644
index 0000000..36103d9
--- /dev/null
+++ b/pkgs/test/README.md
@@ -0,0 +1,874 @@
+[](https://pub.dev/packages/test)
+[](https://pub.dev/packages/test/publisher)
+
+`test` provides a standard way of writing and running tests in Dart.
+
+## Using package:test
+
+* [Writing Tests](#writing-tests)
+* [Running Tests](#running-tests)
+ * [Sharding Tests](#sharding-tests)
+ * [Test Concurrency](#test-concurrency)
+ * [Shuffling Tests](#shuffling-tests)
+ * [Selecting a Test Reporter](#selecting-a-test-reporter)
+ * [Collecting Code Coverage](#collecting-code-coverage)
+ * [Restricting Tests to Certain Platforms](#restricting-tests-to-certain-platforms)
+ * [Platform Selectors](#platform-selectors)
+ * [Compiler Selectors](#compiler-selectors)
+ * [Running Tests on Node.js](#running-tests-on-nodejs)
+* [Asynchronous Tests](#asynchronous-tests)
+* [Running Tests With Custom HTML](#running-tests-with-custom-html)
+ * [Providing a custom HTML template](#providing-a-custom-html-template)
+* [Configuring Tests](#configuring-tests)
+ * [Skipping Tests](#skipping-tests)
+ * [Timeouts](#timeouts)
+ * [Platform-Specific Configuration](#platform-specific-configuration)
+ * [Whole-Package Configuration](#whole-package-configuration)
+* [Tagging Tests](#tagging-tests)
+* [Debugging](#debugging)
+* [Browser/VM Hybrid Tests](#browservm-hybrid-tests)
+* [Support for Other Packages](#support-for-other-packages)
+ * [`build_runner`](#build_runner)
+ * [`term_glyph`](#term_glyph)
+* [Further Reading](#further-reading)
+
+## Writing Tests
+
+Tests are specified using the top-level [`test()`] function.
+Test asserts can be made using [`expect` from `package:matcher`][expect]
+
+[`test()`]: https://pub.dev/documentation/test/latest/test/test.html
+
+[expect]: https://pub.dev/documentation/matcher/latest/expect/expect.html
+
+```dart
+import 'package:test/test.dart';
+
+void main() {
+ test('String.split() splits the string on the delimiter', () {
+ var string = 'foo,bar,baz';
+ expect(string.split(','), equals(['foo', 'bar', 'baz']));
+ });
+
+ test('String.trim() removes surrounding whitespace', () {
+ var string = ' foo ';
+ expect(string.trim(), equals('foo'));
+ });
+}
+```
+
+Tests can be grouped together using the [`group()`] function. Each group's
+description is added to the beginning of its test's descriptions.
+
+[`group()`]: https://pub.dev/documentation/test/latest/test/group.html
+
+```dart
+import 'package:test/test.dart';
+
+void main() {
+ group('String', () {
+ test('.split() splits the string on the delimiter', () {
+ var string = 'foo,bar,baz';
+ expect(string.split(','), equals(['foo', 'bar', 'baz']));
+ });
+
+ test('.trim() removes surrounding whitespace', () {
+ var string = ' foo ';
+ expect(string.trim(), equals('foo'));
+ });
+ });
+
+ group('int', () {
+ test('.remainder() returns the remainder of division', () {
+ expect(11.remainder(3), equals(2));
+ });
+
+ test('.toRadixString() returns a hex string', () {
+ expect(11.toRadixString(16), equals('b'));
+ });
+ });
+}
+```
+
+You can use the [`setUp()`] and [`tearDown()`] functions to share code between
+tests. The `setUp()` callback will run before every test in a group or test
+suite, and `tearDown()` will run after. `tearDown()` will run even if a test
+fails, to ensure that it has a chance to clean up after itself.
+
+```dart
+import 'package:test/test.dart';
+
+void main() {
+ late HttpServer server;
+ late Uri url;
+ setUp(() async {
+ server = await HttpServer.bind('localhost', 0);
+ url = Uri.parse('http://${server.address.host}:${server.port}');
+ });
+
+ tearDown(() async {
+ await server.close(force: true);
+ server = null;
+ url = null;
+ });
+
+ // ...
+}
+```
+
+[`setUp()`]: https://pub.dev/documentation/test/latest/test/setUp.html
+
+[`tearDown()`]: https://pub.dev/documentation/test/latest/test/tearDown.html
+
+## Running Tests
+
+A single test file can be run just using `dart test path/to/test.dart` (as of
+Dart 2.10 - prior sdk versions must use `pub run test` instead of `dart test`).
+
+
+
+Many tests can be run at a time using `dart test path/to/dir`.
+
+
+
+It's also possible to run a test on the Dart VM only by invoking it using `dart
+path/to/test.dart`, but this doesn't load the full test runner and will be
+missing some features.
+
+The test runner accepts one or more path arguments. If there are no path
+arguments the runner defaults to running tests under the `test/` directory. When
+running for `test/` or any other directory, the runner will recursively search
+the directory for files that match the test name pattern `*_test.dart`. The
+pattern can be overridden in `dart_test.yaml`. When a path argument is a file
+instead of a directory it will be run as a test, regardless of the file name.
+Arguments which use shell globbing should avoid including non-test files in the
+path argument. Tests which are run by other test runners may use a different
+default path, such as `integration_test/`. Directories other than `test/` are
+ignored by `dart test` unless passed explicitly as a path to run.
+
+You can select specific tests cases to run by name using `dart test -n "test
+name"`. The string is interpreted as a regular expression, and only tests whose
+description (including any group descriptions) match that regular expression
+will be run. You can also use the `-N` flag to run tests whose names contain a
+plain-text string.
+
+By default, tests are run in the Dart VM, but you can run them in the browser as
+well by passing `dart test -p chrome path/to/test.dart`. `test` will take
+care of starting the browser and loading the tests, and all the results will be
+reported on the command line just like for VM tests. In fact, you can even run
+tests on both platforms with a single command: `dart test -p "chrome,vm"
+path/to/test.dart`.
+
+By default each platform has a default compiler, but some of them support
+more than one compiler. You can choose which compiler to use by passing
+`dart test -c source`, which would run all VM tests from source instead of
+compiling them to kernel. This also supports targeting a specific platform
+using normal platform selectors, like this `dart test -c vm:source`.
+
+### Test Path Queries
+
+Some query parameters are supported on test paths, which allow you to filter the
+tests that will run within just those paths. These filters are merged with any
+global options that are passed, and all filters must match for a test to be ran.
+
+- **name**: Works the same as `--name` (simple contains check).
+ - This is the only option that supports more than one entry.
+- **full-name**: Requires an exact match for the name of the test.
+- **line**: Matches any test that originates from this line in the test suite.
+- **col**: Matches any test that originates from this column in the test suite.
+
+**Example Usage**: `dart test "path/to/test.dart?line=10&col=2"`
+
+#### Line/Col Matching Semantics
+
+The `line` and `col` filters match against the current stack trace taken from
+the invocation to the `test` function, and are considered a match if
+**any frame** in the trace meets **all** of the following criteria:
+
+* The URI of the frame matches the root test suite uri.
+ * This means it will not match lines from imported libraries.
+* If both `line` and `col` are passed, both must match **the same frame**.
+* The specific `line` and `col` to be matched are defined by the tools creating
+ the stack trace. This generally means they are 1 based and not 0 based, but
+ this package is not in control of the exact semantics and they may vary based
+ on platform implementations.
+
+### Sharding Tests
+
+Tests can also be sharded with the `--total-shards` and `--shard-index` arguments,
+allowing you to split up your test suites and run them separately. For example,
+if you wanted to run 3 shards of your test suite, you could run them as follows:
+
+```bash
+dart test --total-shards 3 --shard-index 0 path/to/test.dart
+dart test --total-shards 3 --shard-index 1 path/to/test.dart
+dart test --total-shards 3 --shard-index 2 path/to/test.dart
+```
+Sharding: This refers to the process of splitting up a large test suite into
+smaller subsets (called shards) that can be run independently. Sharding is
+particularly useful for distributed testing, where multiple machines are used
+to run tests simultaneously. By dividing the test suite into smaller subsets,
+you can run tests in parallel across multiple machines, which can significantly
+reduce the overall testing time.
+
+### Test concurrency
+
+Test suites run concurrently by default, using half of the host's CPU cores. Use
+`--concurrency` to control the number of test suites that runs concurrently,
+meaning that multiple tests in independent suites or platforms can run at the
+same time. For example, if you wanted to run the tests on 4 threads, you could
+run the tests as follows:
+
+```bash
+dart test --concurrency=4
+```
+This can speed up the overall testing process, especially if you have a large
+number of test suites.
+
+### Shuffling Tests
+
+Test order can be shuffled with the `--test-randomize-ordering-seed` argument.
+This allows you to shuffle your tests with a specific seed (deterministic) or
+a random seed for each run. For example, consider the following test runs:
+
+```bash
+dart test --test-randomize-ordering-seed=12345
+dart test --test-randomize-ordering-seed=random
+```
+
+Setting `--test-randomize-ordering-seed=0` will have the same effect as not
+specifying it at all, meaning the test order will remain as-is.
+
+### Selecting a Test Reporter
+
+You can adjust the output format of test results using the `--reporter=<option>`
+command line flag. The default format is the `compact` output format - a single
+line, continuously updated as tests are run. When running on the GitHub Actions CI
+however (detected via checking the `GITHUB_ACTIONS` environment variable for `true`),
+the default changes to the `github` output format - a reporter customized
+for that CI/CD system.
+
+The available options for the `--reporter` flag are:
+
+- `compact`: a single, continuously updated line
+- `expanded`: a separate line for each update
+- `github`: a custom reporter for GitHub Actions
+- `json`: a machine-readable format; see https://dart.dev/go/test-docs/json_reporter.md
+
+### Collecting Code Coverage
+
+To collect code coverage, you can run tests with the `--coverage <directory>`
+argument. The directory specified can be an absolute or relative path.
+If a directory does not exist at the path specified, a directory will be
+created. If a directory does exist, files may be overwritten with the latest
+coverage data, if they conflict.
+
+This option will enable code coverage collection on a suite-by-suite basis,
+and the resulting coverage files will be outputted in the directory specified.
+The files can then be formatted using the `package:coverage`
+`format_coverage` executable.
+
+Coverage gathering is currently only implemented for tests run on the Dart VM or
+Chrome.
+
+Here's an example of how to run tests and format the collected coverage to LCOV:
+
+```shell
+## Run Dart tests and output them at directory `./coverage`:
+dart run test --coverage=./coverage
+
+## Activate package `coverage` (if needed):
+dart pub global activate coverage
+
+## Format collected coverage to LCOV (only for directory "lib")
+dart pub global run coverage:format_coverage --packages=.dart_tool/package_config.json --report-on=lib --lcov -o ./coverage/lcov.info -i ./coverage
+
+## Generate LCOV report:
+genhtml -o ./coverage/report ./coverage/lcov.info
+
+## Open the HTML coverage report:
+open ./coverage/report/index.html
+```
+
+* *LCOV is a GNU tool which provides information about what parts of a program are
+ actually executed (i.e. "covered") while running a particular test case.*
+* The binary `genhtml` is one of the LCOV tools.
+* See the LCOV project for more: https://github.com/linux-test-project/lcov
+* See the Homebrew LCOV formula: https://formulae.brew.sh/formula/lcov
+
+### Restricting Tests to Certain Platforms
+
+Some test files only make sense to run on particular platforms. They may use
+`dart:html` or `dart:io`, they might test Windows' particular filesystem
+behavior, or they might use a feature that's only available in Chrome. The
+[`@TestOn`] annotation makes it easy to declare exactly which platforms a test
+file should run on. Just put it at the top of your file, before any `library` or
+`import` declarations:
+
+```dart
+@TestOn('vm')
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+void main() {
+ // ...
+}
+```
+
+[`@TestOn`]: https://pub.dev/documentation/test/latest/test/TestOn-class.html
+
+The string you pass to `@TestOn` is what's called a "platform selector", and it
+specifies exactly which platforms a test can run on. It can be as simple as the
+name of a platform, or a more complex Dart-like boolean expression involving
+these platform names.
+
+You can also declare that your entire package only works on certain platforms by
+adding a [`test_on` field] to your package config file.
+
+[`test_on` field]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#test_on
+
+### Platform Selectors
+
+Platform selectors use the [boolean selector syntax] defined in the
+[`boolean_selector`] package, which is a subset of Dart's expression syntax that
+only supports boolean operations. The following identifiers are defined:
+
+[boolean selector syntax]: https://github.com/dart-lang/boolean_selector/blob/master/README.md
+
+[`boolean_selector`]: https://pub.dev/packages/boolean_selector
+
+* `vm`: Whether the test is running on the command-line Dart VM.
+
+* `chrome`: Whether the test is running on Google Chrome.
+
+* `firefox`: Whether the test is running on Mozilla Firefox.
+
+* `safari`: Whether the test is running on Apple Safari.
+
+* `edge`: Whether the test is running on Microsoft Edge browser.
+
+* `node`: Whether the test is running on Node.js.
+
+* `dart-vm`: Whether the test is running on the Dart VM in any context. It's
+ identical to `!js`.
+
+* `browser`: Whether the test is running in any browser.
+
+* `js`: Whether the test has been compiled to JS. This is identical to
+ `!dart-vm`.
+
+* `blink`: Whether the test is running in a browser that uses the Blink
+ rendering engine.
+
+* `windows`: Whether the test is running on Windows. This can only be `true` if
+ either `vm` or `node` is true.
+
+* `mac-os`: Whether the test is running on MacOS. This can only be `true` if
+ either `vm` or `node` is true.
+
+* `linux`: Whether the test is running on Linux. This can only be `true` if
+ either `vm` or `node` is true.
+
+* `android`: Whether the test is running on Android. If `vm` is false, this will
+ be `false` as well, which means that this *won't* be true if the test is
+ running on an Android browser.
+
+* `ios`: Whether the test is running on iOS. If `vm` is false, this will be
+ `false` as well, which means that this *won't* be true if the test is running
+ on an iOS browser.
+
+* `posix`: Whether the test is running on a POSIX operating system. This is
+ equivalent to `!windows`.
+
+* `dart2js`: Whether the test has been compiled with Dart2Js.
+
+* `dart2wasm`: Whether the test has been compiled with Dart2Wasm.
+
+* `kernel`: Whether the test has been compiled to kernel.
+
+* `source`: Whether the test has been run with no compiler (from source).
+
+For example, if you wanted to run a test on every browser but Chrome, you would
+write `@TestOn('browser && !chrome')`.
+
+### Running Tests on Node.js
+
+The test runner also supports compiling tests to JavaScript and running them on
+[Node.js] by passing `--platform node`. Note that Node has access to *neither*
+`dart:html` nor `dart:io`, so any platform-specific APIs will have to be invoked
+using the [`js`] package. However, it may be useful when testing APIs that are
+meant to be used by JavaScript code.
+
+[Node.js]: https://nodejs.org/en/
+
+[`js`]: https://pub.dev/packages/js
+
+The test runner looks for an executable named `node` (on Mac OS or Linux) or
+`node.exe` (on Windows) on your system path. When compiling Node.js tests, it
+passes `-Dnode=true`, so tests can determine whether they're running on Node
+using [`const bool.fromEnvironment('node')`][bool.fromEnvironment]. It also sets
+`--server-mode`, which will tell the compiler that `dart:html` is not available.
+
+[bool.fromEnvironment]: https://api.dart.dev/stable/dart-core/bool/bool.fromEnvironment.html
+
+If a top-level `node_modules` directory exists, tests running on Node.js can
+import modules from it.
+
+## Asynchronous Tests
+
+Tests written with `async`/`await` will work automatically. The test runner
+won't consider the test finished until the returned `Future` completes.
+
+```dart
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test('Future.value() returns the value', () async {
+ var value = await Future.value(10);
+ expect(value, equals(10));
+ });
+}
+```
+
+### Uncaught Async Errors
+
+Any uncaught asynchronous error throws within the zone that a test is running in
+will cause the test to be considered a failure. This can cause a test which was
+previously considered complete and passing to change into a failure if the
+uncaught async error is raised late. If all test cases within the suite have
+completed this may cause some errors to be missed, or to surface in only some
+runs.
+
+Avoid uncaught async errors by ensuring that all futures have an error handler
+[before they complete as an error][early-handler].
+
+[early-handler]:https://dart.dev/guides/libraries/futures-error-handling#potential-problem-failing-to-register-error-handlers-early
+
+## Running Tests With Custom HTML
+
+By default, the test runner will generate its own empty HTML file for browser
+tests. However, tests that need custom HTML can create their own files. These
+files have three requirements:
+
+* They must have the same name as the test, with `.dart` replaced by `.html`. You can also
+ provide a configuration path to an HTML file if you want it to be reused across all tests.
+ See [Providing a custom HTML template](#providing-a-custom-html-template) below.
+
+* They must contain a `link` tag with `rel="x-dart-test"` and an `href`
+ attribute pointing to the test script.
+
+* They must contain `<script src="packages/test/dart.js"></script>`.
+
+For example, if you had a test called `custom_html_test.dart`, you might write
+the following HTML file:
+
+```html
+<!doctype html>
+<!-- custom_html_test.html -->
+<html>
+ <head>
+ <title>Custom HTML Test</title>
+ <link rel="x-dart-test" href="custom_html_test.dart">
+ <script src="packages/test/dart.js"></script>
+ </head>
+ <body>
+ // ...
+ </body>
+</html>
+```
+
+### Providing a custom HTML template
+
+If you want to share the same HTML file across all tests, you can provide a
+`custom_html_template_path` configuration option to your configuration file.
+This file should follow the rules above, except that instead of the link tag
+add exactly one `{{testScript}}` in the place where you want the template processor to insert it.
+
+You can also optionally use any number of `{{testName}}` placeholders which will be replaced by the test filename.
+
+The template can't be named like any test file, as that would clash with using the
+custom HTML mechanics. In such a case, an error will be thrown.
+
+For example:
+
+```yaml
+custom_html_template_path: html_template.html.tpl
+```
+
+```html
+<!doctype html>
+<!-- html_template.html.tpl -->
+<html>
+ <head>
+ <title>{{testName}} Test</title>
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+ </head>
+ <body>
+ // ...
+ </body>
+</html>
+```
+
+## Configuring Tests
+
+### Skipping Tests
+
+If a test, group, or entire suite isn't working yet, and you just want it to stop
+complaining, you can mark it as "skipped". The test or tests won't be run, and,
+if you supply a reason why, that reason will be printed. In general, skipping
+tests indicates that they should run but is temporarily not working. If they're
+fundamentally incompatible with a platform, [`@TestOn`/`testOn`][TestOn]
+should be used instead.
+
+[TestOn]: #restricting-tests-to-certain-platforms
+
+To skip a test suite, put a `@Skip` annotation at the top of the file:
+
+```dart
+@Skip('currently failing (see issue 1234)')
+
+import 'package:test/test.dart';
+
+void main() {
+ // ...
+}
+```
+
+The string you pass should describe why the test is skipped. You don't have to
+include it, but it's a good idea to document why the test isn't running.
+
+Groups and individual tests can be skipped by passing the `skip` parameter. This
+can be either `true` or a String describing why the test is skipped. For
+example:
+
+```dart
+import 'package:test/test.dart';
+
+void main() {
+ group('complicated algorithm tests', () {
+ // ...
+ }, skip: "the algorithm isn't quite right");
+
+ test('error-checking test', () {
+ // ...
+ }, skip: 'TODO: add error-checking.');
+}
+```
+
+### Timeouts
+
+By default, tests will time out after 30 seconds of inactivity. The timeout
+applies to deadlocks or cases where the test stops making progress, it does not
+ensure that an overall test case or test suite completes within any set time.
+
+Timeouts can be configured on a per-test, -group, or -suite basis. To change the
+timeout for a test suite, put a `@Timeout` annotation at the top of the file:
+
+```dart
+@Timeout(Duration(seconds: 45))
+
+import 'package:test/test.dart';
+
+void main() {
+ // ...
+}
+```
+
+In addition to setting an absolute timeout, you can set the timeout relative to
+the default using `@Timeout.factor`. For example, `@Timeout.factor(1.5)` will
+set the timeout to one and a half times as long as the default—45 seconds.
+
+Timeouts can be set for tests and groups using the `timeout` parameter. This
+parameter takes a `Timeout` object just like the annotation. For example:
+
+```dart
+import 'package:test/test.dart';
+
+void main() {
+ group('slow tests', () {
+ // ...
+
+ test('even slower test', () {
+ // ...
+ }, timeout: Timeout.factor(2));
+ }, timeout: Timeout(Duration(minutes: 1)));
+}
+```
+
+Nested timeouts apply in order from outermost to innermost. That means that
+"even slower test" will take two minutes to time out, since it multiplies the
+group's timeout by 2.
+
+### Platform-Specific Configuration
+
+Sometimes a test may need to be configured differently for different platforms.
+Windows might run your code slower than other platforms, or your DOM
+manipulation might not work right on Safari yet. For these cases, you can use
+the `@OnPlatform` annotation and the `onPlatform` named parameter to `test()`
+and `group()`. For example:
+
+```dart
+@OnPlatform({
+ // Give Windows some extra wiggle-room before timing out.
+ 'windows': Timeout.factor(2)
+})
+
+import 'package:test/test.dart';
+
+void main() {
+ test('do a thing', () {
+ // ...
+ }, onPlatform: {
+ 'safari': Skip('Safari is currently broken (see #1234)')
+ });
+}
+```
+
+Both the annotation and the parameter take a map. The map's keys are [platform
+selectors](#platform-selectors) which describe the platforms for which the
+specialized configuration applies. Its values are instances of some of the same
+annotation classes that can be used for a suite: `Skip` and `Timeout`. A value
+can also be a list of these values.
+
+If multiple platforms match, the configuration is applied in order from first to
+last, just as they would in nested groups. This means that for configuration
+like duration-based timeouts, the last matching value wins.
+
+You can also set up global platform-specific configuration using the
+[package configuration file][configuring platforms].
+
+[configuring platforms]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-platforms
+
+### Tagging Tests
+
+Tags are short strings that you can associate with tests, groups, and suites.
+They don't have any built-in meaning, but they're very useful nonetheless: you
+can associate your own custom configuration with them, or you can use them to
+easily filter tests so you only run the ones you need to.
+
+Tags are defined using the `@Tags` annotation for suites and the `tags` named
+parameter to `test()` and `group()`. For example:
+
+```dart
+@Tags(['browser'])
+
+import 'package:test/test.dart';
+
+void main() {
+ test('successfully launches Chrome', () {
+ // ...
+ }, tags: 'chrome');
+
+ test('launches two browsers at once', () {
+ // ...
+ }, tags: ['chrome', 'firefox']);
+}
+```
+
+If the test runner encounters a tag that wasn't declared in the
+[package configuration file][configuring tags], it'll print a warning, so be
+sure to include all your tags there. You can also use the file to provide
+default configuration for tags, like giving all `browser` tests twice as much
+time before they time out.
+
+[configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags
+
+Tests can be filtered based on their tags by passing command line flags. The
+`--tags` or `-t` flag will cause the test runner to only run tests with the
+given tags, and the `--exclude-tags` or `-x` flag will cause it to only run
+tests *without* the given tags. These flags also support
+[boolean selector syntax]. For example, you can pass `--tags "(chrome ||
+firefox) && !slow"` to select quick Chrome or Firefox tests.
+
+Note that tags must be valid Dart identifiers, although they may also contain
+hyphens.
+
+### Whole-Package Configuration
+
+For configuration that applies across multiple files, or even the entire
+package, `test` supports a configuration file called `dart_test.yaml`. At its
+simplest, this file can contain the same sort of configuration that can be
+passed as command-line arguments:
+
+```yaml
+# This package's tests are very slow. Double the default timeout.
+timeout: 2x
+
+# This is a browser-only package, so test on chrome by default.
+platforms: [chrome]
+```
+
+The configuration file sets new defaults. These defaults can still be overridden
+by command-line arguments, just like the built-in defaults. In the example
+above, you could pass `--platform firefox` to run on Firefox.
+
+A configuration file can do much more than just set global defaults. See
+[the full documentation][package config] for more details.
+
+[package config]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md
+
+### Compiler flags
+
+The test runner does not support general purpose flags to control compilation
+such as `-D` defines or flags like `--no-sound-null-safety`. In most cases it is
+preferable to avoid writing tests that depend on the fine-grained compiler
+configuration. For instance to choose between sound and unsound null safety,
+prefer to choose a language version for each test which has the desired behavior
+by default - choose a language version below `2.12` to disable sound null
+safety, and a language version above `2.12` to enable sound null safety. When
+fine-grained configuration is unavoidable, the approach varies by platform.
+
+Compilation for browser and node tests can be configured by passing arguments to
+`dart compile js` with `--dart2js-args` options.
+
+Fine-grained compilation configuration is not supported for the VM. Any
+configuration which impacts runtime behavior for the entire VM, such as `-D`
+defines (when used for non-const values) and runtime behavior experiments, will
+influence both the test runner and the isolates spawned to run test suites.
+Experiments which are breaking may cause incompatibilities with the test runner.
+These may be specified with a `DART_VM_OPTIONS` environment variable when
+running with `pub run test`, or by passing them to the `dart` command before the
+`test` subcommand when using `dart test`.
+
+## Debugging
+
+Tests can be debugged interactively using platforms' built-in development tools.
+Tests running on browsers can use those browsers' development consoles to inspect
+the document, set breakpoints, and step through code. Those running on the Dart
+VM use [Dart DevTools][devtools].
+
+[devtools]: https://dart.dev/tools/dart-devtools
+
+The first step when debugging is to pass the `--pause-after-load` flag to the
+test runner. This pauses the browser after each test suite has loaded, so that
+you have time to open the development tools and set breakpoints. For the Dart VM
+it will print the remote debugger URL.
+
+Once you've set breakpoints, either click the big arrow in the middle of the web
+page or press Enter in your terminal to start the tests running. When you hit a
+breakpoint, the runner will open its own debugging console in the terminal that
+controls how tests are run. You can type "restart" there to re-run your test as
+many times as you need to figure out what's going on.
+
+Normally, browser tests are run in hidden iframes. However, when debugging, the
+iframe for the current test suite is expanded to fill the browser window so you
+can see and interact with any HTML it renders. Note that the Dart animation may
+still be visible behind the iframe; to hide it, just add a `background-color` to
+the page's HTML.
+
+## Browser/VM Hybrid Tests
+
+Code that's written for the browser often needs to talk to some kind of server.
+Maybe you're testing the HTML served by your app, or maybe you're writing a
+library that communicates over WebSockets. We call tests that run code on both
+the browser and the VM **hybrid tests**.
+
+Hybrid tests use one of two functions: [`spawnHybridCode()`] and
+[`spawnHybridUri()`]. Both of these spawn Dart VM
+[isolates][dart:isolate] that can import `dart:io` and other VM-only libraries.
+The only difference is where the code from the isolate comes from:
+`spawnHybridCode()` takes a chunk of actual Dart code, whereas
+`spawnHybridUri()` takes a URL. They both return a [`StreamChannel`] that
+communicates with the hybrid isolate. For example:
+
+[`spawnHybridCode()`]: https://pub.dev/documentation/test/latest/test/spawnHybridCode.html
+
+[`spawnHybridUri()`]: https://pub.dev/documentation/test/latest/test/spawnHybridUri.html
+
+[dart:isolate]: https://api.dart.dev/stable/dart-isolate/dart-isolate-library.html
+
+[`StreamChannel`]: https://pub.dev/documentation/stream_channel/latest/stream_channel/StreamChannel-class.html
+
+```dart
+// ## test/web_socket_server.dart
+
+// The library loaded by spawnHybridUri() can import any packages that your
+// package depends on, including those that only work on the VM.
+import 'package:shelf/shelf_io.dart' as io;
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+// Once the hybrid isolate starts, it will call the special function
+// hybridMain() with a StreamChannel that's connected to the channel
+// returned spawnHybridCode().
+hybridMain(StreamChannel channel) async {
+ // Start a WebSocket server that just sends "hello!" to its clients.
+ var server = await io.serve(webSocketHandler((webSocket, _) {
+ webSocket.sink.add('hello!');
+ }), 'localhost', 0);
+
+ // Send the port number of the WebSocket server to the browser test, so
+ // it knows what to connect to.
+ channel.sink.add(server.port);
+}
+
+
+// ## test/web_socket_test.dart
+
+@TestOn('browser')
+
+import 'dart:html';
+
+import 'package:test/test.dart';
+
+void main() {
+ test('connects to a server-side WebSocket', () async {
+ // Each spawnHybrid function returns a StreamChannel that communicates with
+ // the hybrid isolate. You can close this channel to kill the isolate.
+ var channel = spawnHybridUri('web_socket_server.dart');
+
+ // Get the port for the WebSocket server from the hybrid isolate.
+ var port = await channel.stream.first;
+
+ var socket = WebSocket('ws://localhost:$port');
+ var message = await socket.onMessage.first;
+ expect(message.data, equals('hello!'));
+ });
+}
+```
+
+
+
+**Note**: If you write hybrid tests, be sure to add a dependency on the
+`stream_channel` package, since you're using its API!
+
+## Support for Other Packages
+
+### `build_runner`
+
+If you are using `package:build_runner` to build your package, then you will
+need a dependency on `build_test` in your `dev_dependencies`, and then you can
+use the `pub run build_runner test` command to run tests.
+
+To supply arguments to `package:test`, you need to separate them from your build
+args with a `--` argument. For example, running all web tests in release mode
+would look like this `pub run build_runner test --release -- -p vm`.
+
+### `term_glyph`
+
+The [`term_glyph`] package provides getters for Unicode glyphs with
+ASCII alternatives. `test` ensures that it's configured to produce ASCII when
+the user is running on Windows, where Unicode isn't supported. This ensures that
+testing libraries can use Unicode on POSIX operating systems without breaking
+Windows users.
+
+[`term_glyph`]: https://pub.dev/packages/term_glyph
+
+## Further Reading
+
+Check out the [API docs] for detailed information about all the functions
+available to tests.
+
+[API docs]: https://pub.dev/documentation/test/latest/
+
+The test runner also supports a machine-readable JSON-based reporter. This
+reporter allows the test runner to be wrapped and its progress presented in
+custom ways (for example, in an IDE). See [the protocol documentation][json] for
+more details.
+
+[json]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/json_reporter.md
diff --git a/pkgs/test/bin/test.dart b/pkgs/test/bin/test.dart
new file mode 100644
index 0000000..0b40282
--- /dev/null
+++ b/pkgs/test/bin/test.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2015, 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.
+
+export 'package:test/src/executable.dart';
diff --git a/pkgs/test/dart_test.yaml b/pkgs/test/dart_test.yaml
new file mode 100644
index 0000000..49af036
--- /dev/null
+++ b/pkgs/test/dart_test.yaml
@@ -0,0 +1,55 @@
+# Disallow duplicate test names in this package
+allow_duplicate_test_names: false
+
+# Fold frames from helper packages we use in our tests, but not from test
+# itself.
+fold_stack_frames:
+ except:
+ - shelf_test_handler
+ - stream_channel
+ - test_descriptor
+ - test_process
+
+presets:
+ # "-P terse-trace" folds frames from test's implementation to make the output
+ # less verbose when
+ terse-trace:
+ fold_stack_frames:
+ except: [test]
+
+tags:
+ browser:
+ timeout: 2x
+
+ # Browsers can sometimes randomly time out while starting, especially on
+ # Travis which is pretty slow. Don't retry locally because it makes
+ # debugging more annoying.
+ presets: {travis: {retry: 3}}
+
+ dart2js:
+ add_tags: [browser]
+ timeout: 2x
+
+ firefox:
+ add_tags: [dart2js]
+ test_on: linux
+ chrome: {add_tags: [dart2js]}
+
+ safari:
+ add_tags: [dart2js]
+ test_on: mac-os
+
+ edge:
+ add_tags: [dart2js]
+ test_on: windows
+
+ # Tests that run pub. These tests may need to be excluded when there are local
+ # dependency_overrides.
+ pub:
+ timeout: 2x
+
+ # Tests that use Node.js. These tests may need to be excluded on systems that
+ # don't have Node installed.
+ node:
+ timeout: 2x
+ test_on: linux
diff --git a/pkgs/test/doc/architecture.md b/pkgs/test/doc/architecture.md
new file mode 100644
index 0000000..c378e0c
--- /dev/null
+++ b/pkgs/test/doc/architecture.md
@@ -0,0 +1,236 @@
+# Test Package Architecture
+
+* [Code Organization](#code-organization)
+ * [Frontend](#frontend)
+ * [Backend](#backend)
+ * [Runner](#runner)
+* [Lifecycle of a Test Run](#lifecycle-of-a-test-run)
+ * [Loading a Suite on the VM](#loading-a-suite-on-the-vm)
+ * [Loading a Suite in the Browser](#loading-a-suite-in-the-browser)
+
+## Code Organization
+
+From a user's perspective, the test package provides two main pieces of
+functionality: an API for defining tests, and a command-line tool to run those
+tests. The structure of the package reflects this division. The code is divided
+into three main sections: the frontend, the backend, and the runner.
+
+### Frontend
+
+The [`lib/src/frontend`][frontend] directory contains APIs that are exposed to
+the user when they import `package:test/test.dart`. This includes core functions
+such as `expect()` and `expectAsync()`, test-specific matchers such as
+`throwsA()` and `prints()`, and annotation classes such as `TestOn` and
+`Timeout`. The functions that define the top-level structure of the test, such
+as `test()` and `group()`, are defined in `lib/test.dart`, but they can be
+thought of as frontend functions as well.
+
+[frontend]: https://github.com/dart-lang/test/tree/master/lib/src/frontend
+
+The frontend communicates with the backend using zone-scoped getters.
+[`Invoker.current`][Invoker] provides access to the current test case to
+matchers like [`completion()`][completion], for example to control when
+it completes. Structural functions use [`Declarer.current`][Declarer] to
+gradually build up an in-memory representation of a test suite. The runner is in
+charge of setting up these variables, but the frontend never communicates with
+the runner directly.
+
+[Invoker]: https://github.com/dart-lang/test/blob/master/lib/src/backend/invoker.dart
+[completion]: https://pub.dev/documentation/matcher/latest/expect/completion.html
+[Declarer]: https://github.com/dart-lang/test/blob/master/lib/src/backend/declarer.dart
+
+### Backend
+
+The [`lib/src/backend`][backend] directory contains classes that represent the
+in-memory structure of a test suite. A [`Suite`][Suite] represents a single test
+file, and class contains a tree of [`Group`][Group]s, each of which contains
+many [`Test`][Test]s. These classes are built using a [`Declarer`][Declarer].
+
+[backend]: https://github.com/dart-lang/test/tree/master/lib/src/backend
+[Suite]: https://github.com/dart-lang/test/blob/master/lib/src/backend/suite.dart
+[Group]: https://github.com/dart-lang/test/blob/master/lib/src/backend/group.dart
+[Test]: https://github.com/dart-lang/test/blob/master/lib/src/backend/test.dart
+
+The backend also contains the [`Invoker`][Invoker], which is responsible for
+actually running an individual test case—including tracking how many outstanding
+asynchronous callbacks are pending, handling exceptions, and timing out the test
+if it takes too long. The `Invoker` provides information about the status of a
+running test as streams and futures on a [`LiveTest`][LiveTest] object.
+
+[LiveTest]: https://github.com/dart-lang/test/blob/master/lib/src/backend/live_test.dart
+
+The backend provides a bridge between the frontend and the runner. The runner
+sets up the `Declarer` and starts the `Invoker`, which the frontend functions
+then communicate with directly.
+
+### Runner
+
+The [`lib/src/runner`][runner] directory contains the code that's executed when
+`dart test` is invoked. It's in charge of locating test files, loading them,
+executing them, and communicating their results to the user. It's also by far
+the biggest section. For more information on the runner architecture, see
+[Lifecycle of a Test Run](#lifecycle-of-a-test-suite) below.
+
+[runner]: https://github.com/dart-lang/test/tree/master/lib/src/runner
+
+## Lifecycle of a Test Run
+
+To understand generally how the test runner works, let's look at an example run.
+When the user first invokes `dart test`, the command-line arguments and
+[configuration files][] are combined into a single
+[`Configuration`][Configuration] object which is passed into the
+[`Runner`][Runner] class. The `Runner` is mostly just glue: it starts up the
+various components necessary for a test run, and connects them to one another.
+It's also in charge of handling certain `Configuration` flags.
+
+[configuration files]: https://github.com/dart-lang/test/blob/master/doc/configuration.md
+[Configuration]: https://github.com/dart-lang/test/tree/master/lib/src/runner/configuration.dart
+[Runner]: https://github.com/dart-lang/test/tree/master/lib/src/runner.dart
+
+The first thing the runner starts is the [`Engine`][Engine]. The engine iterates
+through a test suite's tests and invokes them in order. It knows how to handle
+set-up and tear-down functions, and how to combine the output of multiple test
+suites running concurrently. It exposes its progress through a collection of
+getters and streams that provide access to individual [`LiveTest`][LiveTest]s.
+
+[Engine]: https://github.com/dart-lang/test/tree/master/lib/src/runner/engine.dart
+
+The runner then passes the `Engine` to a [`Reporter`][Reporter], which listens
+to the `Engine`'s streams and exposes the information there to the user, usually
+by printing human-readable text. [`CompactReporter`][CompactReporter] is the
+default on Posix platforms, but others may be selected based on the
+`Configuration`. Nearly everything the user sees comes through the reporter.
+
+[Reporter]: https://github.com/dart-lang/test/tree/master/lib/src/runner/reporter.dart
+[CompactReporter]: https://github.com/dart-lang/test/tree/master/lib/src/runner/reporter/compact.dart
+
+The `Engine` and `Reporter` can't do much of anything, though, without any test
+suites to run. The next step is to load those suites. The [`Loader`][Loader] is
+in charge of this part. It takes in file or directory paths and finds all the
+test files they contain—by default any files matching `*_test.dart`. It then
+proceeds to load each file on all the platforms specified in the `Configuration`
+that's also supported by the test suite.
+
+[Loader]: https://github.com/dart-lang/test/tree/master/lib/src/runner/loader.dart
+
+The specifics of loading suites differs based on whether the platform is a
+browser or the Dart VM. I'll cover each platform below, but for now let's stick
+to what they have in common. Every platform will emit a
+[`LoadSuite`][LoadSuite], which is a synthetic [`Suite`][Suite] containing a
+single test that, when invoked, produces the actual `Suite` defined in the test
+file.
+
+[LoadSuite]: https://github.com/dart-lang/test/tree/master/lib/src/runner/load_suite.dart
+
+Wrapping the loading process in a synthetic `Suite` gives us the very useful
+invariant that *all test errors occur within a `Suite`*. Loading can fail in all
+sorts of ways—the code might not compile, the `main()` method might throw, the
+browser might not be installed, and so on. Locating those errors within a
+`Suite` means that the `Engine` and `Reporter`, which already know how to deal
+with test errors, can deal with load errors in exactly the same way. It makes
+the load process a little more complex, but it makes everything else a lot
+cleaner.
+
+Once a `Suite` has been loaded, the runner does a little post-processing to make
+sure the `Configuration` is handled properly. It filters out tests whose tags
+don't match the `--tags` flag, or whose names don't match the `--name` flag.
+Then it passes the resulting `Suite`s on to the `Engine` and they begin to run.
+
+### Loading a Suite on the VM
+
+Let's start with looking at how suites are loaded on the Dart VM, since the
+process is substantially simpler than loading them on a browser. This loading is
+handled by the [`VMPlatform`][VMPlatform], which extends the
+[`PlatformPlugin`][PlatformPlugin] class. [Eventually][issue 49], we plan to
+support a user-accessible platform plugin API, so we model platforms as plugins
+to prepare for that.
+
+[VMPlatform]: https://github.com/dart-lang/test/tree/master/lib/src/runner/vm/platform.dart
+[PlatformPlugin]: https://github.com/dart-lang/test/tree/master/lib/src/runner/plugin/platform.dart
+[issue 49]: https://github.com/dart-lang/test/issues/49
+
+In its simplest form, a `PlatformPlugin`'s responsibility is just to create a
+[`StreamChannel`][StreamChannel] that connects the test runner to a remote
+isolate—everything else is handled by helper functions. The `VMPlatform` uses
+[`Isolate`][Isolate]s to dynamically load its test suites, and then communicates
+with them using an [`IsolateChannel`][IsolateChannel]. It passes in a `data:`
+URI containing Dart code that imports the user's code, and runs that code in the
+context of the [`serializeSuite()`][remote platform helpers] helper, and the
+`PlatformPlugin` superclass deserializes it on the other side using
+[`deserializeSuite()`][platform helpers].
+
+[StreamChannel]: https://pub.dev/packages/stream_channel
+[Isolate]: https://api.dart.dev/stable/dart-isolate/Isolate-class.html
+[IsolateChannel]: https://pub.dev/documentation/stream_channel/latest/stream_channel/IsolateChannel-class.html
+[remote platform helpers]: https://github.com/dart-lang/test/tree/master/lib/src/runner/plugin/remote_platform_helpers.dart
+[platform helpers]: https://github.com/dart-lang/test/tree/master/lib/src/runner/plugin/platform_helpers.dart
+
+When a test suite is serialized and deserialized, it's not just converted to and
+from some static representation like JSON. The [`Engine`][Engine] needs
+fine-grained control over the remote suite, and the [`Reporter`][Reporter] needs
+fine-grained access to the [`LiveTest`][LiveTest]s it emits. To make this work,
+the helper functions use the [`MultiChannel`][MultiChannel] class to tunnel
+streams for each test through the main `IsolateChannel`. Each test has its own
+virtual channel that gets a message when the test runner calls
+[`Test.load()`][Test], and that sends messages back to indicate the progress of
+the test.
+
+Information about these virtual channels, as well as test names and metadata,
+are bundled up into a JSON object and sent over the `IsolateChannel` to be
+deserialized. The deserialization process then converts them into
+[`RunnerTest`][RunnerTest]s within a [`RunnerSuite`][RunnerSuite], which the
+`Engine` can then run just like normal `Test`s in a normal [`Suite`][Suite].
+
+[MultiChannel]: https://pub.dev/documentation/stream_channel/latest/stream_channel/MultiChannel-class.html
+[RunnerTest]: https://github.com/dart-lang/test/tree/master/lib/src/runner/runner_test.dart
+[RunnerSuite]: https://github.com/dart-lang/test/tree/master/lib/src/runner/runner_suite.dart
+
+### Loading a Suite in the Browser
+
+The [`BrowserPlatform`][BrowserPlatform] class also extends
+[`PlatformPlugin`][PlatformPlugin], but rather than just emitting a
+[`StreamChannel`][StreamChannel] and letting the plugin helpers do the rest, it
+takes more control over the loading process. It emits its own
+[`RunnerSuite`][RunnerSuite], which allows it to expose its own
+[`Environment`][Environment] to enable debugging.
+
+[BrowserPlatform]: https://github.com/dart-lang/test/tree/master/lib/src/runner/browser/platform.dart
+[Environment]: https://github.com/dart-lang/test/tree/master/lib/src/runner/environment.dart
+
+Whereas the [`VMPlatform`][VMPlatform] loads each separate suite in isolation,
+the `BrowserPlatform` shares a substantial amount of resources between suites.
+All suites load their code from a single HTTP server, which is managed by the
+platform. This server provides access to compiled JavaScript for other browsers,
+and to HTML files that bootstrap the tests.
+
+In addition to sharing a server, when multiple suites are loaded for the same
+browser, they all share a tab within that browser. Each separate browser is
+controlled by its own [`BrowserManager`][BrowserManager], which uses
+`WebSocket`s to communicate with Dart code running in the main frame—also known
+as [the host][host].
+
+[BrowserManager]: https://github.com/dart-lang/test/tree/master/lib/src/runner/browser/browser_manager.dart
+[host]: https://github.com/dart-lang/test/tree/master/lib/src/runner/browser/static/host.dart
+
+Each browser is spawned with a tab pointing to
+`packages/test/src/runner/browser/static/index.html`, the host page. The host's
+code then opens a `WebSocket` connection to a dynamically-generated URL. This
+URL tells the `BrowserPlatform` which `BrowserManager` to send the `WebSocket`
+to.
+
+To load a suite for this browser, the `BrowserPlatform` passes the URL for that
+suite's HTML file to the `BrowserManager`, which in turn sends it down to the
+host page. The host opens this HTML in an iframe, opens a
+[`StreamChannel`][StreamChannel] with this iframe using
+[`Window.postMessage()`][Window.postMessage]. It then tunnels this channel
+through the `WebSocket` connection, again using [`MultiChannel`][MultiChannel],
+so that the `BrowserManager` has a direct line to the iframe where the tests are
+defined.
+
+[Window.postMessage]: https://api.dart.dev/stable/dart-html/Window/postMessage.html
+
+From this point forward the process is similar to `VMPlatform`. The iframe
+serializes its test suite using [`serializeSuite()`][remote platform helpers],
+and the `BrowserManager` deserializes it using
+[`deserializeSuite()`][platform helpers]. It's then forwarded to the `Loader`
+via the `BrowserPlatform`.
diff --git a/pkgs/test/doc/configuration.md b/pkgs/test/doc/configuration.md
new file mode 100644
index 0000000..6541b97
--- /dev/null
+++ b/pkgs/test/doc/configuration.md
@@ -0,0 +1,890 @@
+Each package may include a configuration file that applies to the package as a
+whole. This file can be used to provide custom defaults for various options, to
+define configuration for multiple files, and more.
+
+The file is named `dart_test.yaml` and lives at the root of the package, next to
+the package's pubspec. Like the pubspec, it's a [YAML][] file. Here's an
+example:
+
+[YAML]: http://yaml.org/
+
+```yaml
+# This package's tests are very slow. Double the default timeout.
+timeout: 2x
+
+# This is a browser-only package, so test on chrome by default.
+platforms: [chrome]
+
+tags:
+ # Integration tests are even slower, so increase the timeout again.
+ integration: {timeout: 2x}
+
+ # Sanity tests are quick and verify that nothing is obviously wrong. Declaring
+ # the tag allows us to tag tests with it without getting warnings.
+ sanity:
+```
+
+* [Test Configuration](#test-configuration)
+ * [`timeout`](#timeout)
+ * [`ignore-timeouts`](#ignore-timeouts)
+ * [`verbose_trace`](#verbose_trace)
+ * [`chain_stack_traces`](#chain_stack_traces)
+ * [`js_trace`](#js_trace)
+ * [`skip`](#skip)
+ * [`retry`](#retry)
+ * [`test_on`](#test_on)
+ * [`allow_test_randomization`](#allow_test_randomization)
+ * [`allow_duplicate_test_names`](#allow_duplicate_test_names)
+* [Runner Configuration](#runner-configuration)
+ * [`include`](#include)
+ * [`paths`](#paths)
+ * [`filename`](#filename)
+ * [`names`](#names)
+ * [`plain_names`](#plain_names)
+ * [`include_tags`](#include_tags)
+ * [`exclude_tags`](#exclude_tags)
+ * [`platforms`](#platforms)
+ * [`concurrency`](#concurrency)
+ * [`pause_after_load`](#pause_after_load)
+ * [`run_skipped`](#run_skipped)
+ * [`reporter`](#reporter)
+ * [`file_reporters`](#file_reporters)
+ * [`fold_stack_frames`](#fold_stack_frames)
+ * [`custom_html_template_path`](#custom_html_template_path)
+* [Configuring Tags](#configuring-tags)
+ * [`tags`](#tags)
+ * [`add_tags`](#add_tags)
+* [Configuring Platforms](#configuring-platforms)
+ * [`on_os`](#on_os)
+ * [`on_platform`](#on_platform)
+ * [`override_platforms`](#override_platforms)
+ * [`define_platforms`](#define_platforms)
+ * [Browser and Node.js Settings](#browser-and-nodejs-settings)
+ * [`arguments`](#arguments)
+ * [`executable`](#executable)
+ * [`headless`](#headless)
+* [Configuration Presets](#configuration-presets)
+ * [`presets`](#presets)
+ * [`add_presets`](#add_presets)
+* [Global Configuration](#global-configuration)
+
+## Test Configuration
+
+There are two major categories of configuration field: "test" and "runner". Test
+configuration controls how individual tests run, while
+[runner configuration](#runner-configuration) controls the test runner as a
+whole. Both types of fields may be used at the top level of a configuration
+file. However, because different tests can have different test configuration,
+only test configuration fields may be used to [configure tags](#tags) or
+[platforms](#on_platform).
+
+### `timeout`
+
+This field indicates how much time the test runner should allow a test to remain
+inactive before it considers that test to have failed. It has three possible
+formats:
+
+* The string "none" indicates that tests should never time out.
+
+* A number followed by a unit abbreviation indicates an exact time. For example,
+ "1m" means a timeout of one minute, and "30s" means a timeout of thirty
+ seconds. Multiple numbers can be combined, as in "1m 30s".
+
+* A number followed by "x" indicates a multiple. This is applied to the default
+ value of 30s.
+
+```yaml
+timeout: 1m
+```
+
+### `ignore-timeouts`
+
+This field disables all timeouts for all tests. This can be useful when debugging, so tests don't time out during debug sessions. It defaults to `false`.
+
+```yaml
+ignore-timeouts: true
+```
+
+### `verbose_trace`
+
+This boolean field controls whether or not stack traces caused by errors are
+trimmed to remove internal stack frames. This includes frames from the Dart core
+libraries, the [`stack_trace`][stack_trace] package, and the `test` package
+itself. It defaults to `false`.
+
+[stack_trace]: https://pub.dev/packages/stack_trace
+
+```yaml
+verbose_trace: true
+```
+
+### `chain_stack_traces`
+
+This boolean field controls whether or not stack traces are chained.
+Disabling [`stack trace chaining`][stack trace chaining] will improve
+performance for heavily asynchronous code at the cost of debuggability.
+
+[stack trace chaining]: https://github.com/dart-lang/stack_trace/blob/master/README.md#stack-chains
+
+```yaml
+chain_stack_traces: false
+```
+
+### `js_trace`
+
+This boolean field controls whether or not stack traces caused by errors that
+occur while running Dart compiled to JS are converted back to Dart style. This
+conversion uses the source map generated by `dart2js` to approximate the
+original Dart line, column, and in some cases member name for each stack frame.
+It defaults to `false`.
+
+```yaml
+js_trace: true
+```
+
+### `skip`
+
+This field controls whether or not tests are skipped. It's usually applied to
+[specific tags](#configuring-tags) rather than used at the top level. Like the
+`skip` parameter for [`test()`][test], it can either be a boolean indicating
+whether the tests are skipped or a string indicating the reason they're skipped.
+
+[test]: https://pub.dev/documentation/test/latest/test/test.html
+
+```yaml
+tags:
+ chrome:
+ skip: "Our Chrome launcher is busted. See issue 1234."
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `retry`
+
+This int field controls how many times a test is retried upon failure.
+
+```yaml
+tags:
+ chrome:
+ retry: 3 # Retry chrome failures 3 times.
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `test_on`
+
+This field declares which platforms a test supports. It takes a
+[platform selector][platform selectors] and only allows tests to run on
+platforms that match the selector. It's often used with
+[specific tags](#configuring-tags) to ensure that certain features will only be
+tested on supported platforms.
+
+[platform selectors]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-selectors
+
+```yaml
+tags:
+ # Test on browsers other than firefox
+ some_feature: {test_on: "browser && !firefox"}
+```
+
+The field can also be used at the top level of the configuration file to
+indicate that the entire package only supports a particular platform. If someone
+tries to run the tests on an unsupported platform, the runner will print a
+warning and skip that platform.
+
+```yaml
+# This package uses dart:io.
+test_on: vm
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `allow_test_randomization`
+
+This can be used to disable test randomization for certain tests, regardless
+of the `--test-randomize-ordering-seed` configuration.
+
+This is typically useful when a subset of your tests are order dependent, but
+you want to run the other ones with randomized ordering.
+
+```yaml
+tags:
+ doNotRandomize:
+ allow_test_randomization: false
+```
+
+### `allow_duplicate_test_names`
+
+This can be used to allow multiple tests in the same suite to have the same
+name. This is disabled by default because it is usually an indication of a
+mistake, and it causes problems with IDE integrations which run tests by name.
+
+It can be disabled for all tests:
+
+```yaml
+allow_duplicate_test_names: true
+```
+
+Or for tagged tests only (useful if migrating to enable this):
+
+```yaml
+tags:
+ allowDuplicates:
+ allow_duplicate_test_names: true
+```
+
+It cannot be globally enabled or configured on the command line - this would
+make it so that tests might pass on one users machine but not anothers which
+should be avoided.
+
+## Runner Configuration
+
+Unlike [test configuration](#test-configuration), runner configuration affects
+the test runner as a whole rather than individual tests. It can only be used at
+the top level of the configuration file.
+
+### `include`
+
+This field loads another configuration file. It's useful for repositories that
+contain multiple packages and want to share configuration among them. It takes a
+(usually relative) `file:` URL.
+
+If you have a repository with the following structure:
+
+```
+repo/
+ dart_test_base.yaml
+ package/
+ test/
+ dart_test.yaml
+ pubspec.yaml
+```
+
+```yaml
+# repo/dart_test_base.yaml
+filename: "test_*.dart"
+```
+
+```yaml
+# repo/package/dart_test.yaml
+include: ../dart_test_base.yaml
+```
+
+...tests in the `package` directory will use configuration from both
+`dart_test_base.yaml` and `dart_test.yaml`.
+
+The local configuration file's fields take precedence over those from an
+included file, so it's possible to override a base configuration.
+
+### `paths`
+
+This field indicates the default paths that the test runner should run. These
+paths are usually directories, although single filenames may be used as well.
+Paths must be relative, and they must be in URL format so that they're
+compatible across operating systems. This defaults to `[test]`.
+
+```yaml
+paths: [dart/test]
+
+paths:
+- test/instantaneous
+- test/fast
+- test/middling
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `filename`
+
+This field indicates the filename pattern that the test runner uses to find test
+files in directories. All files in directories passed on the command line (or in
+directories in [`paths`](#paths), if none are passed) whose basenames match this
+pattern will be loaded and run as tests.
+
+This supports the full [glob syntax][]. However, since it's only compared
+against a path's basename, path separators aren't especially useful. It defaults
+to `"*_test.dart"`.
+
+```yaml
+filename: "test_*.dart"
+```
+
+[glob syntax]: https://github.com/dart-lang/glob#syntax
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `names`
+
+This field causes the runner to only run tests whose names match the given
+regular expressions. A test's name must match *all* regular expressions in
+`names`, as well as containing all strings in [`plain_names`](#plain_names), in
+order to be run.
+
+This is usually used in a [preset](#configuration-presets) to make it possible
+to quickly select a given set of tests.
+
+```yaml
+presets:
+ # Pass "-P chrome" to run only Chrome tests.
+ chrome:
+ names:
+ - "^browser:"
+ - "[Cc]hrome"
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `plain_names`
+
+This field causes the runner to only run tests whose names contain the given
+strings. A test's name must contain *all* strings in `plain_names`, as well as
+matching all regular expressions in [`names`](#names), in order to be run.
+
+This is usually used in a [preset](#configuration-presets) to make it possible
+to quickly select a given set of tests.
+
+```yaml
+presets:
+ # Pass "-P feature" to run only tests with "feature name" in the name.
+ feature:
+ plain_names:
+ - "feature name"
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `include_tags`
+
+This field causes the runner to only run tests whose tags match the given
+[boolean selector][]. If both `include_tags` and [`exclude_tags`](#exclude_tags)
+are used, the exclusions take precedence.
+
+[boolean selector]: https://github.com/dart-lang/boolean_selector/blob/master/README.md
+
+This is usually used in a [preset](#configuration-preset) to make it possible to
+quickly select a set of tests.
+
+```yaml
+presets:
+ # Pass "-P windowless" to run tests that don't open browser windows.
+ windowless:
+ include_tags: !browser || content-shell
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `exclude_tags`
+
+This field causes the runner not to run tests whose tags match the given
+[boolean selector][]. If both [`include_tags`](#include_tags) and `exclude_tags`
+are used, the exclusions take precedence.
+
+This is usually used in a [preset](#configuration-preset) to make it possible to
+quickly select a set of tests.
+
+```yaml
+presets:
+ # Pass "-P windowless" to run tests that don't open browser windows.
+ windowless:
+ exclude_tags: browser && !content-shell
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `platforms`
+
+This field indicates which platforms tests should run on by default. It allows
+the same platform identifiers that can be passed to `--platform`. If multiple
+platforms are included, the test runner will default to running tests on all of
+them. This defaults to `[vm]`.
+
+```yaml
+platforms: [chrome]
+
+platforms:
+- chrome
+- firefox
+```
+
+### `compilers`
+
+This field indicates which compilers tests should be compiled with by default.
+It allows the same compiler selectors that can be passed to `--compiler`. If
+a given platform has no supported compiler configured, it will use its default.
+
+```yaml
+compilers: [source]
+
+compilers:
+- source
+```
+
+### `concurrency`
+
+This field indicates the default number of test suites to run in parallel. More
+parallelism can improve the overall speed of running tests up to a point, but
+eventually it just adds more memory overhead without any performance gain. This
+defaults to approximately half the number of processors on the current machine.
+If it's set to 1, only one test suite will run at a time.
+
+```yaml
+concurrency: 3
+```
+
+### `pause_after_load`
+
+This field indicates that the test runner should pause for debugging after each
+test suite is loaded but before its tests are executed. If it's set,
+[`concurrency`](#concurrency) is automatically set to 1 and
+[`timeout`](#timeout) is automatically set to `none`.
+
+This is usually used in a [preset](#configuration-presets).
+
+```yaml
+presets:
+ # Pass "-P debug" to enable debugging configuration
+ debug:
+ pause_after_load: true
+ exclude_tags: undebuggable
+ reporter: expanded
+```
+
+### `run_skipped`
+
+This field indicates that the test runner should run tests even if they're
+marked as skipped.
+
+This is usually used in a [preset](#configuration-presets).
+
+```yaml
+presets:
+ # Pass "-P all" to run all tests
+ debug:
+ run_skipped: true
+ paths: ["test/", "extra_test/"]
+```
+
+### `reporter`
+
+This field indicates the default reporter to use. It may be set to "compact",
+"expanded", or "json" (although why anyone would want to default to JSON is
+beyond me). It defaults to "expanded" on Windows and "compact" everywhere else.
+
+```yaml
+reporter: expanded
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `file_reporters`
+
+This field specifies additional reporters to use that will write their output to
+a file rather than stdout. It should be a map of reporter names to filepaths.
+
+```yaml
+file_reporters:
+ json: reports/tests.json
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### `fold_stack_frames`
+
+This field controls which packages' stack frames will be folded away
+when displaying stack traces. Packages contained in the `except`
+option will be folded. If `only` is provided, all packages not
+contained in this list will be folded. By default,
+frames from the `test` package and the `stream_channel`
+package are folded.
+
+```yaml
+fold_stack_frames:
+ except:
+ - test
+ - stream_channel
+```
+
+Sample stack trace, note the absence of `package:test`
+and `package:stream_channel`:
+```
+test/sample_test.dart 7:5 main.<fn>
+===== asynchronous gap ===========================
+dart:async _Completer.completeError
+test/sample_test.dart 8:3 main.<fn>
+===== asynchronous gap ===========================
+dart:async _asyncThenWrapperHelper
+test/sample_test.dart 5:27 main.<fn>
+```
+
+### `custom_html_template_path`
+
+This field specifies the path of the HTML template file to be used for tests run in an HTML environment.
+Any HTML file that is named the same as the test and in the same directory will take precedence over the template.
+For more information about the usage of this option see [Providing a custom HTML template](https://github.com/dart-lang/test/blob/master/README.md#providing-a-custom-html-template)
+
+## Configuring Tags
+
+### `tags`
+
+The `tag` field can be used to apply [test configuration](#test-configuration)
+to all tests [with a given tag][tagging tests] or set of tags. It takes a map
+from tag selectors to configuration maps. These configuration maps are just like
+the top level of the configuration file, except that they may not contain
+[runner configuration](#runner-configuration).
+
+[tagging tests]: https://github.com/dart-lang/test/blob/master/README.md#tagging-tests
+
+```yaml
+tags:
+ # Integration tests need more time to run.
+ integration:
+ timeout: 1m
+```
+
+Tags may also have no configuration associated with them. The test runner prints
+a warning whenever it encounters a tag it doesn't recognize, and this the best
+way to tell it that a tag exists.
+
+```yaml
+# We occasionally want to use --tags or --exclude-tags on these tags.
+tags:
+ # A test that spawns a browser.
+ browser:
+
+ # A test that needs Ruby installed.
+ ruby:
+```
+
+You can also use [boolean selector syntax][] to define configuration that
+involves multiple tags. For example:
+
+[boolean selector syntax]: https://github.com/dart-lang/boolean_selector/blob/master/README.md
+
+```yaml
+tags:
+ # Tests that invoke sub-processes tend to be a little slower.
+ ruby || python:
+ timeout: 1.5x
+```
+
+Tag configuration is applied at whatever level the tag appears—so if a group is
+tagged as `integration`, its timeout will take precedence over the suite's
+timeout but not any tests'. If the group itself had a timeout declared, the
+group's explicit timeout would take precedence over the tag.
+
+If multiple tags appear at the same level, and they have conflicting
+configurations, the test runner *does not guarantee* what order they'll be
+resolved in. In practice, conflicting configuration is pretty unlikely and it's
+easy to just explicitly specify what you want on the test itself.
+
+This field counts as [test configuration](#test-configuration). It is not
+supported in the [global configuration file](#global-configuration).
+
+### `add_tags`
+
+This field adds additional tags. It's technically
+[test configuration](#test-configuration), but it's usually used in more
+specific contexts. For example, when included in a tag's configuration, it can
+be used to enable tag inheritance, where adding one tag implicitly adds another
+as well. It takes a list of tag name strings.
+
+```yaml
+tags:
+ # Any test that spawns a browser.
+ browser:
+ timeout: 2x
+
+ # Tests that spawn specific browsers. These automatically get the browser tag
+ # as well.
+ chrome: {add_tags: [browser]}
+ firefox: {add_tags: [browser]}
+ safari: {add_tags: [browser]}
+ edge: {add_tags: [browser]}
+```
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+## Configuring Platforms
+
+There are two different kinds of platform configuration.
+[Operating system configuration](#on_os) cares about the operating system on
+which test runner is running. It sets global configuration for the runner on
+particular OSes. [Test platform configuration](#on_platform), on the other hand,
+cares about the platform the *test* is running on (like the
+[`@OnPlatform` annotation][@OnPlatform]). It sets configuration for particular
+tests that are running on matching platforms.
+
+[@OnPlatform]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-specific-configuration
+
+### `on_os`
+
+This field applies configuration when specific operating systems are being used.
+It takes a map from operating system identifiers (the same ones that are used in
+[platform selectors][]) to configuration maps that are applied on those
+operating systems. These configuration maps are just like the top level of the
+configuration file, and allow any fields that may be used in the context where
+`on_os` was used.
+
+```yaml
+on_os:
+ windows:
+ # Both of these are the defaults anyway, but let's be explicit about it.
+ color: false
+ runner: expanded
+
+ # My Windows machine is real slow.
+ timeout: 2x
+
+ # My Linux machine has SO MUCH RAM.
+ linux:
+ concurrency: 500
+```
+
+This field counts as [test configuration](#test-configuration). If it's used in
+a context that only allows test configuration, it may only contain test
+configuration.
+
+### `on_platform`
+
+This field applies configuration to tests that are run on specific platforms. It
+takes a map from [platform selectors][] to configuration maps that are applied
+to tests run on those platforms. These configuration maps are just like the top
+level of the configuration file, except that they may not contain
+[runner configuration](#runner-configuration).
+
+```yaml
+# Our code is kind of slow on Blink and WebKit.
+on_platform:
+ chrome || safari: {timeout: 2x}
+```
+
+**Note**: operating system names that appear in `on_platform` refer to tests
+that are run on the Dart VM under that operating system. To configure all tests
+when running on a particular operating system, use [`on_os`](#on_os) instead.
+
+This field counts as [test configuration](#test-configuration).
+
+### `override_platforms`
+
+This field allows you to customize the settings for built-in test platforms. It
+takes a map from platform identifiers to settings for those platforms. For example:
+
+```yaml
+override_platforms:
+ chrome:
+ # The settings to override for this platform.
+ settings:
+ executable: chromium
+```
+
+This tells the test runner to use the `chromium` executable for Chrome tests. It
+calls that executable with the same logic and flags it normally uses for Chrome.
+
+Each platform can define exactly which settings it supports. All browsers and
+Node.js support [the same settings](#browser-and-node-js-settings), but the VM
+doesn't support any settings and so can't be overridden.
+
+### `define_platforms`
+
+You can define new platforms in terms of old ones using the `define_platforms`
+field. This lets you define variants of existing platforms without overriding
+the old ones. This field takes a map from the new platform identifiers to
+definitions for those platforms. For example:
+
+```yaml
+define_platforms:
+ # This identifier is used to select the platform with the --platform flag.
+ chromium:
+ # A human-friendly name for the platform.
+ name: Chromium
+
+ # The identifier for the platform that this is based on.
+ extends: chrome
+
+ # Settings for the new child platform.
+ settings:
+ executable: chromium
+```
+
+Once this is defined, you can run `dart test -p chromium` and it will run
+those tests in the Chromium browser, using the same logic it normally uses for
+Chrome. You can even use `chromium` in platform selectors; for example, you
+might pass `testOn: "chromium"` to declare that a test is Chromium-specific.
+User-defined platforms also count as their parents, so Chromium will run tests
+that say `testOn: "chrome"` as well.
+
+Each platform can define exactly which settings it supports. All browsers and
+Node.js support [the same settings](#browser-and-node-js-settings), but the VM
+doesn't support any settings and so can't be extended.
+
+This field is not supported in the
+[global configuration file](#global-configuration).
+
+### Browser and Node.js Settings
+
+All built-in browser platforms, as well as the built-in Node.js platform,
+provide the same settings that can be set using
+[`define_platforms`](#define_platforms), which control how their executables are
+invoked.
+
+#### `arguments`
+
+The `arguments` field provides extra arguments to the executable. It takes a
+string, and parses it in the same way as the POSIX shell:
+
+```yaml
+override_platforms:
+ firefox:
+ settings:
+ arguments: -headless
+```
+
+#### `executable`
+
+The `executable` field tells the test runner where to look for the executable to
+use to start the subprocess. It has three sub-keys, one for each supported
+operating system, which each take a path or an executable name:
+
+```yaml
+define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+
+ settings:
+ executable:
+ linux: chromium
+ mac_os: /Applications/Chromium.app/Contents/MacOS/Chromium
+ windows: Chromium\Application\chrome.exe
+```
+
+Executables can be defined in three ways:
+
+* As a plain basename, with no path separators. These executables are passed
+ directly to the OS, which looks them up using the `PATH` environment variable.
+
+* As an absolute path, which is used as-is.
+
+* **Only on Windows**, as a relative path. The test runner will look up this
+ path relative to the `LOCALAPPATA`, `PROGRAMFILES`, and `PROGRAMFILES(X86)`
+ environment variables, in that order.
+
+If a platform is omitted, it defaults to using the built-in executable location.
+
+As a shorthand, you can also define the same executable for all operating
+systems:
+
+```yaml
+define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+
+ settings:
+ executable: chromium
+```
+
+#### `headless`
+
+The `headless` field says whether or not to run the browser in headless mode.
+It defaults to `true`. It's currently only supported for Chrome:
+
+```yaml
+override_platforms:
+ chrome:
+ settings:
+ headless: false
+```
+
+Note that headless mode is always disabled when debugging.
+
+## Configuration Presets
+
+*Presets* are collections of configuration that can be explicitly selected on
+the command-line. They're useful for quickly selecting options that are
+frequently used together, for providing special configuration for continuous
+integration systems, and for defining more complex logic than can be expressed
+directly using command-line arguments.
+
+Presets can be selected on the command line using the `--preset` or `-P` flag.
+Any number of presets can be selected this way; if they conflict, the last one
+selected wins. Only presets that are defined in the configuration file may be
+selected.
+
+### `presets`
+
+This field defines which presets are available to select. It takes a map from
+preset names to configuration maps that are applied when those presets are
+selected. These configuration maps are just like the top level of the
+configuration file, and allow any fields that may be used in the context where
+`presets` was used.
+
+```yaml
+presets:
+ # Use this when you need completely un-munged stack traces.
+ debug:
+ verbose_trace: false
+ js_trace: true
+
+ # Shortcut for running only browser tests.
+ browser:
+ paths:
+ - test/runner/browser
+```
+
+The `presets` field counts as [test configuration](#test-configuration). It can
+be useful to use it in combination with other fields for advanced preset
+behavior.
+
+```yaml
+tags:
+ chrome:
+ skip: "Our Chrome launcher is busted. See issue 1234."
+
+ # Pass -P force to verify that the launcher is still busted.
+ presets: {force: {skip: false}}
+```
+
+### `add_presets`
+
+This field selects additional presets. It's technically
+[runner configuration](#runner-configuration), but it's usually used in more
+specific contexts. For example, when included in a preset's configuration, it
+can be used to enable preset inheritance, where selecting one preset implicitly
+selects another as well. It takes a list of preset name strings.
+
+```yaml
+presets:
+ # Shortcut for running only browser tests.
+ browser:
+ paths: [test/runner/browser]
+
+ # Shortcut for running only Chrome tests.
+ chrome:
+ filename: "chrome_*_test.dart"
+ add_presets: [browser]
+```
+
+## Global Configuration
+
+The test runner also supports a global configuration file. On Windows, this
+file's local defaults to `%LOCALAPPDATA%\DartTest.yaml`. On Unix, it defaults to
+`~/.dart_test.yaml`. It can also be explicitly set using the `DART_TEST_CONFIG`
+environment variable.
+
+The global configuration file supports a subset of the fields supported by the
+package-specific configuration file. In general, it doesn't support fields that
+are closely tied to the structure of an individual package. Fields that are not
+supported in the global configuration file say so in their documentation.
diff --git a/pkgs/test/doc/json_reporter.md b/pkgs/test/doc/json_reporter.md
new file mode 100644
index 0000000..be18ddb
--- /dev/null
+++ b/pkgs/test/doc/json_reporter.md
@@ -0,0 +1,504 @@
+JSON Reporter Protocol
+======================
+
+The test runner supports a JSON reporter which provides a machine-readable
+representation of the test runner's progress. This reporter is intended for use
+by IDEs and other tools to present a custom view of the test runner's operation
+without needing to parse output intended for humans.
+
+Note that the test runner is highly asynchronous, and users of this protocol
+shouldn't make assumptions about the ordering of events beyond what's explicitly
+specified in this document. It's possible for events from multiple tests to be
+intertwined, for a single test to emit an error after it completed successfully,
+and so on.
+
+## Usage
+
+Pass the `--reporter json` command-line flag to the test runner to activate the
+JSON reporter.
+
+ dart test --reporter json <path-to-test-file>
+
+You may also use the `--file-reporter` option to enable the JSON reporter output
+to a file, in addition to another reporter writing to stdout.
+
+ dart test --file-reporter json:reports/tests.json <path-to-test-file>
+
+The JSON stream will be emitted via standard output. It will be a stream of JSON
+objects, separated by newlines.
+
+See `json_reporter.schema.json` for a formal description of the protocol schema.
+See `test/runner/json_reporter_test.dart` for some sample output.
+
+## Compatibility
+
+The protocol emitted by the JSON reporter is considered part of the public API
+of the `test` package, and is subject to its [semantic versioning][semver]
+restrictions. In particular:
+
+[semver]: https://dart.dev/tools/pub/versioning#semantic-versions
+
+* No new feature will be added to the protocol without increasing the test
+ package's minor version number.
+
+* No breaking change will be made to the protocol without increasing the test
+ package's major version number.
+
+The following changes are not considered breaking. This is not necessarily a
+comprehensive list.
+
+* Adding a new attribute to an existing object.
+
+* Adding a new type of any object with a `type` parameter.
+
+* Adding new test state values.
+
+## Reading this Document
+
+Each major type of JSON object used by the protocol is described by a class.
+Classes have names which are referred to in this document, but are not used as
+part of the protocol. Classes have typed attributes, which refer to the types
+and names of attributes in the JSON objects. If an attribute's type is another
+class, that refers to a nested object. The special type `List<...>` indicates a
+JSON list of the given type.
+
+Classes can "extend" one another, meaning that the subclass has all the
+attributes of the superclass. Concrete subclasses can be distinguished by the
+specific value of their `type` attribute. Classes may be abstract, indicating
+that only their subclasses will ever be used.
+
+## Events
+
+### Event
+
+```
+abstract class Event {
+ // The type of the event.
+ //
+ // This is always one of the subclass types listed below.
+ String type;
+
+ // The time (in milliseconds) that has elapsed since the test runner started.
+ int time;
+}
+```
+
+This is the root class of the protocol. All root-level objects emitted by the
+JSON reporter will be subclasses of `Event`.
+
+### StartEvent
+
+```
+class StartEvent extends Event {
+ String type = "start";
+
+ // The version of the JSON reporter protocol being used.
+ //
+ // This is a semantic version, but it reflects only the version of the
+ // protocol—it's not identical to the version of the test runner itself.
+ String protocolVersion;
+
+ // The version of the test runner being used.
+ //
+ // This is null if for some reason the version couldn't be loaded.
+ String? runnerVersion;
+
+ // The pid of the VM process running the tests.
+ int pid;
+}
+```
+
+A single start event is emitted before any other events. It indicates that the
+test runner has started running.
+
+### AllSuitesEvent
+
+```
+class AllSuitesEvent extends Event {
+ String type = "allSuites";
+
+ /// The total number of suites that will be loaded.
+ int count;
+}
+```
+
+A single suite count event is emitted once the test runner knows the total
+number of suites that will be loaded over the course of the test run. Because
+this is determined asynchronously, its position relative to other events (except
+`StartEvent`) is not guaranteed.
+
+### SuiteEvent
+
+```
+class SuiteEvent extends Event {
+ String type = "suite";
+
+ /// Metadata about the suite.
+ Suite suite;
+}
+```
+
+A suite event is emitted before any `GroupEvent`s for groups in a given test
+suite. This is the only event that contains the full metadata about a suite;
+future events will refer to the suite by its opaque ID.
+
+### DebugEvent
+
+```
+class DebugEvent extends Event {
+ String type = "debug";
+
+ /// The suite for which debug information is reported.
+ int suiteID;
+
+ /// The HTTP URL for the Dart Observatory, or `null` if the Observatory isn't
+ /// available for this suite.
+ String? observatory;
+
+ /// The HTTP URL for the remote debugger for this suite's host page, or `null`
+ /// if no remote debugger is available for this suite.
+ String? remoteDebugger;
+}
+```
+
+A debug event is emitted after (although not necessarily directly after) a
+`SuiteEvent`, and includes information about how to debug that suite. It's only
+emitted if the `--debug` flag is passed to the test runner.
+
+Note that the `remoteDebugger` URL refers to a remote debugger whose protocol
+may differ based on the browser the suite is running on. You can tell which
+protocol is in use by the `Suite.platform` field for the suite with the given
+ID. Since the same browser instance is used for multiple suites, different
+suites may have the same `host` URL, although only one suite at a time will be
+active when `--pause-after-load` is passed.
+
+### GroupEvent
+
+```
+class GroupEvent extends Event {
+ String type = "group";
+
+ /// Metadata about the group.
+ Group group;
+}
+```
+
+A group event is emitted before any `TestStartEvent`s for tests in a given
+group. This is the only event that contains the full metadata about a group;
+future events will refer to the group by its opaque ID.
+
+This includes the implicit group at the root of each suite, which has a `null`
+name. However, it does *not* include implicit groups for the virtual suites
+generated to represent loading test files.
+
+If the group is skipped, a single `TestStartEvent` will be emitted for a test
+within the group, followed by a `TestDoneEvent` marked as skipped. The
+`group.metadata` field should *not* be used for determining whether a group is
+skipped.
+
+### TestStartEvent
+
+```
+class TestStartEvent extends Event {
+ String type = "testStart";
+
+ // Metadata about the test that started.
+ Test test;
+}
+```
+
+An event emitted when a test begins running. This is the only event that
+contains the full metadata about a test; future events will refer to the test by
+its opaque ID.
+
+If the test is skipped, its `TestDoneEvent` will have `skipped` set to `true`.
+The `test.metadata` should *not* be used for determining whether a test is
+skipped.
+
+### MessageEvent
+
+```
+class MessageEvent extends Event {
+ String type = "print";
+
+ // The ID of the test that printed a message.
+ int testID;
+
+ // The type of message being printed.
+ String messageType;
+
+ // The message that was printed.
+ String message;
+}
+```
+
+A `MessageEvent` indicates that a test emitted a message that should be
+displayed to the user. The `messageType` field indicates the precise type of
+this message. Different message types should be visually distinguishable.
+
+A message of type "print" comes from a user explicitly calling `print()`.
+
+A message of type "skip" comes from a test, or a section of a test, being
+skipped. A skip message shouldn't be considered the authoritative source that a
+test was skipped; the `TestDoneEvent.skipped` field should be used instead.
+
+### ErrorEvent
+
+```
+class ErrorEvent extends Event {
+ String type = "error";
+
+ // The ID of the test that experienced the error.
+ int testID;
+
+ // The result of calling toString() on the error object.
+ String error;
+
+ // The error's stack trace, in the stack_trace package format.
+ String stackTrace;
+
+ // Whether the error was a TestFailure.
+ bool isFailure;
+}
+```
+
+A `ErrorEvent` indicates that a test encountered an uncaught error. Note
+that this may happen even after the test has completed, in which case it should
+be considered to have failed.
+
+If a test is asynchronous, it may encounter multiple errors, which will result
+in multiple `ErrorEvent`s.
+
+### TestDoneEvent
+
+```
+class TestDoneEvent extends Event {
+ String type = "testDone";
+
+ // The ID of the test that completed.
+ int testID;
+
+ // The result of the test.
+ String result;
+
+ // Whether the test's result should be hidden.
+ bool hidden;
+
+ // Whether the test (or some part of it) was skipped.
+ bool skipped;
+}
+```
+
+An event emitted when a test completes. The `result` attribute indicates the
+result of the test:
+
+* `"success"` if the test had no errors.
+
+* `"failure"` if the test had a `TestFailure` but no other errors.
+
+* `"error"` if the test had an error other than a `TestFailure`.
+
+If the test encountered an error, the `TestDoneEvent` will be emitted after the
+corresponding `ErrorEvent`.
+
+The `hidden` attribute indicates that the test's result should be hidden and not
+counted towards the total number of tests run for the suite. This is true for
+virtual tests created for loading test suites, `setUpAll()`, and
+`tearDownAll()`. Only successful tests will be hidden.
+
+Note that it's possible for a test to encounter an error after completing. In
+that case, it should be considered to have failed, but no additional
+`TestDoneEvent` will be emitted. If a previously-hidden test encounters an
+error after completing, it should be made visible.
+
+### DoneEvent
+
+```
+class DoneEvent extends Event {
+ String type = "done";
+
+ // Whether all tests succeeded (or were skipped).
+ //
+ // Will be `null` if the test runner was close before all tests completed
+ // running.
+ bool? success;
+}
+```
+
+An event indicating the result of the entire test run. This will be the final
+event emitted by the reporter.
+
+## Other Classes
+
+### Test
+
+```
+class Test {
+ // An opaque ID for the test.
+ int id;
+
+ // The name of the test, including prefixes from any containing groups.
+ String name;
+
+ // The ID of the suite containing this test.
+ int suiteID;
+
+ // The IDs of groups containing this test, in order from outermost to
+ // innermost.
+ List<int> groupIDs;
+
+ // The (1-based) line on which the test was defined, or `null`.
+ int? line;
+
+ // The (1-based) column on which the test was defined, or `null`.
+ int? column;
+
+ // The URL for the file in which the test was defined, or `null`.
+ String? url;
+
+ // The (1-based) line in the original test suite from which the test
+ // originated.
+ //
+ // Will only be present if `root_url` is different from `url`.
+ int? root_line;
+
+ // The (1-based) line on in the original test suite from which the test
+ // originated.
+ //
+ // Will only be present if `root_url` is different from `url`.
+ int? root_column;
+
+ // The URL for the original test suite in which the test was defined.
+ //
+ // Will only be present if different from `url`.
+ String? root_url;
+
+ // This field is deprecated and should not be used.
+ Metadata metadata;
+}
+```
+
+A single test case. The test's ID is unique in the context of this test run.
+It's used elsewhere in the protocol to refer to this test without including its
+full representation.
+
+Most tests will have at least one group ID, representing the implicit root
+group. However, some may not; these should be treated as having no group
+metadata.
+
+The `line`, `column`, and `url` fields indicate the location the `test()`
+function was called to create this test. They're treated as a unit: they'll
+either all be `null` or they'll all be non-`null`. The URL is always absolute,
+and may be a `package:` URL.
+
+### Suite
+
+```
+class Suite {
+ // An opaque ID for the group.
+ int id;
+
+ // The platform on which the suite is running.
+ String platform;
+
+ // The path to the suite's file, or `null` if that path is unknown.
+ String? path;
+}
+```
+
+A test suite corresponding to a loaded test file. The suite's ID is unique in
+the context of this test run. It's used elsewhere in the protocol to refer to
+this suite without including its full representation.
+
+A suite's platform is one of the platforms that can be passed to the
+`--platform` option, or `null` if there is no platform (for example if the file
+doesn't exist at all). Its path is either absolute or relative to the root of
+the current package.
+
+### Group
+
+```
+class Group {
+ // An opaque ID for the group.
+ int id;
+
+ // The name of the group, including prefixes from any containing groups.
+ String name;
+
+ // The ID of the suite containing this group.
+ int suiteID;
+
+ // The ID of the group's parent group, unless it's the root group.
+ int? parentID;
+
+ // The number of tests (recursively) within this group.
+ int testCount;
+
+ // The (1-based) line on which the group was defined, or `null`.
+ int? line;
+
+ // The (1-based) column on which the group was defined, or `null`.
+ int? column;
+
+ // The URL for the file in which the group was defined, or `null`.
+ String? url;
+
+ // This field is deprecated and should not be used.
+ Metadata metadata;
+}
+```
+
+A group containing test cases. The group's ID is unique in the context of this
+test run. It's used elsewhere in the protocol to refer to this group without
+including its full representation.
+
+The implicit group at the root of each test suite has `null` `name` and
+`parentID` attributes.
+
+The `line`, `column`, and `url` fields indicate the location the `group()`
+function was called to create this group. They're treated as a unit: they'll
+either all be `null` or they'll all be non-`null`. The URL is always absolute,
+and may be a `package:` URL.
+
+### Metadata
+
+```
+class Metadata {
+ bool skip;
+
+ // The reason the tests was skipped, or `null` if it wasn't skipped.
+ String? skipReason;
+}
+```
+
+The metadata class is deprecated and should not be used.
+
+## Remote Debugger APIs
+
+When running browser tests with `--pause-after-load`, the test package embeds a
+few APIs in the JavaScript context of the host page. These allow tools to
+control the debugging process in the same way a user might do from the command
+line. They can be accessed by connecting to the remote debugger using the
+[`DebugEvent.remoteDebugger`](#DebugEvent) URL.
+
+All APIs are defined as methods on the top-level `dartTest` object. The
+following methods are available:
+
+### `resume()`
+
+Calling `resume()` when the test runner is paused causes it to resume running
+tests. If the test runner is not paused, it won't do anything. When
+`--pause-after-load` is passed, the test runner will pause after loading each
+suite but before any tests are run.
+
+This gives external tools a chance to use the remote debugger protocol to set
+breakpoints before tests have begun executing. They can start the test runner
+with `--pause-after-load`, connect to the remote debugger using the
+[`DebugEvent.remoteDebugger`](#DebugEvent) URL, set breakpoints, then call
+`dartTest.resume()` in the host frame when they're finished.
+
+### `restartCurrent()`
+
+Calling `restartCurrent()` when the test runner is running a test causes it to
+re-run that test once it completes its current run. It's intended to be called
+when the browser is paused, as at a breakpoint.
diff --git a/pkgs/test/doc/json_reporter.schema.json b/pkgs/test/doc/json_reporter.schema.json
new file mode 100644
index 0000000..25417e2
--- /dev/null
+++ b/pkgs/test/doc/json_reporter.schema.json
@@ -0,0 +1,220 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "An event emitted by the test package's JSON reporter",
+
+ "definitions": {
+ "GroupEntry": {
+ "required": [
+ "id",
+ "suiteID",
+ "metadata",
+ "line",
+ "column",
+ "url"
+ ],
+ "properties": {
+ "id": {"type": "integer", "minimum": 0},
+ "suiteID": {"type": "integer", "minimum": 0},
+ "metadata": {"$ref": "#/definitions/Metadata"}
+ },
+ "oneOf": [
+ {
+ "properties": {
+ "line": {"type": "integer", "minimum": 1},
+ "column": {"type": "integer", "minimum": 1},
+ "url": {"type": "string", "format": "uri"}
+ }
+ },
+ {
+ "properties": {
+ "line": {"type": "null"},
+ "column": {"type": "null"},
+ "url": {"type": "null"}
+ }
+ }
+ ]
+ },
+
+ "Test": {
+ "allOf": [{"$ref": "#/definitions/GroupEntry"}],
+ "required": [
+ "name",
+ "groupIDs"
+ ],
+ "properties": {
+ "name": {"type": "string"},
+ "groupIDs": {
+ "type": "array",
+ "items": {"type": "integer", "minimum": 0}
+ }
+ }
+ },
+
+ "Suite": {
+ "required": ["id", "platform", "path"],
+ "properties": {
+ "id": {"type": "integer", "minimum": 0},
+ "platform": {
+ "oneOf": [{"type": "string"}, {"type": "null"}]
+ },
+ "path": {"type": "string"}
+ }
+ },
+
+ "Group": {
+ "allOf": [{"$ref": "#/definitions/GroupEntry"}],
+ "required": ["name"],
+ "properties": {
+ "parentID": {
+ "oneOf": [{"type": "integer", "minimum": 0}, {"type": "null"}]
+ },
+ "name": {
+ "oneOf": [{"type": "string"}, {"type": "null"}]
+ },
+ "testCount": {"type": "integer", "minimum": 0}
+ }
+ },
+
+ "Metadata": {
+ "required": ["skip", "skipReason"],
+ "properties": {
+ "skip": {"type": "boolean"},
+ "skipReason": {
+ "oneOf": [{"type": "string"}, {"type": "null"}]
+ }
+ }
+ }
+ },
+
+ "required": ["type", "time"],
+ "properties": {
+ "time": {"type": "integer", "minimum": 0},
+ "type": {"type": "string"}
+ },
+
+ "oneOf": [
+ {
+ "title": "StartEvent",
+ "required": ["protocolVersion", "runnerVersion"],
+ "properties": {
+ "type": {"enum": ["start"]},
+ "pid": {"type": "integer", "minimum": 0},
+ "protocolVersion": {"type": "string", "pattern": "^0\\.1\\."},
+ "runnerVersion": {
+ "oneOf": [{"type": "string"}, {"type": "null"}]
+ }
+ }
+ },
+
+ {
+ "title": "TestStartEvent",
+ "required": ["test"],
+ "properties": {
+ "type": {"enum": ["testStart"]},
+ "test": {"$ref": "#/definitions/Test"}
+ }
+ },
+
+ {
+ "title": "AllSuitesEvent",
+ "required": ["count"],
+ "properties": {
+ "type": {"enum": ["allSuites"]},
+ "count": {"type": "integer", "minimum": 0}
+ }
+ },
+
+ {
+ "title": "SuiteEvent",
+ "required": ["suite"],
+ "properties": {
+ "type": {"enum": ["suite"]},
+ "suite": {"$ref": "#/definitions/Suite"}
+ }
+ },
+
+ {
+ "title": "DebugEvent",
+ "required": ["suiteID"],
+ "properties": {
+ "type": {"enum": ["debug"]},
+ "suiteID": {"type": "integer", "minimum": 0},
+ "observatory": {
+ "oneOf": [{"type": "string", "format": "uri"}, {"type": "null"}]
+ },
+ "remoteDebugger": {
+ "oneOf": [{"type": "string", "format": "uri"}, {"type": "null"}]
+ }
+ }
+ },
+
+ {
+ "title": "GroupEvent",
+ "required": ["group"],
+ "properties": {
+ "type": {"enum": ["group"]},
+ "group": {"$ref": "#/definitions/Group"}
+ }
+ },
+
+ {
+ "title": "MessageEvent",
+ "required": ["testID", "message", "type"],
+ "properties": {
+ "type": {"enum": ["print"]},
+ "testID": {"type": "integer", "minimum": 0},
+ "message": {"type": "string"},
+ "messageType": {"type": "string", "enum": ["print", "skip"]}
+ }
+ },
+
+ {
+ "title": "ErrorEvent",
+ "required": ["testID", "error", "stackTrace", "isFailure"],
+ "properties": {
+ "type": {"enum": ["error"]},
+ "testID": {"type": "integer", "minimum": 0},
+ "error": {"type": "string"},
+ "stackTrace": {"type": "string"},
+ "isFailure": {"type": "boolean"}
+ }
+ },
+
+ {
+ "title": "TestDoneEvent",
+ "required": ["testID", "result", "hidden", "skipped"],
+ "properties": {
+ "type": {"enum": ["testDone"]},
+ "testID": {"type": "integer", "minimum": 0},
+ "result": {"type": "string", "enum": ["success", "failure", "error"]},
+ "hidden": {"type": "boolean"},
+ "skipped": {"type": "boolean"}
+ }
+ },
+
+ {
+ "title": "DoneEvent",
+ "required": ["success"],
+ "properties": {
+ "type": {"enum": ["done"]},
+ "success": {"type": "boolean"}
+ }
+ },
+
+ {
+ "title": "FutureEvent",
+ "description":
+ "A placeholder event to allow new events to be added in the future.",
+ "properties": {
+ "type": {
+ "not": {
+ "enum": [
+ "start", "testStart", "allSuites", "suite", "group", "print",
+ "error", "testDone", "done", "debug"
+ ]
+ }
+ }
+ }
+ }
+ ]
+}
diff --git a/pkgs/test/doc/package_config.md b/pkgs/test/doc/package_config.md
new file mode 120000
index 0000000..422034a
--- /dev/null
+++ b/pkgs/test/doc/package_config.md
@@ -0,0 +1 @@
+configuration.md
\ No newline at end of file
diff --git a/pkgs/test/image/hybrid.png b/pkgs/test/image/hybrid.png
new file mode 100644
index 0000000..554a28c
--- /dev/null
+++ b/pkgs/test/image/hybrid.png
Binary files differ
diff --git a/pkgs/test/image/test1.gif b/pkgs/test/image/test1.gif
new file mode 100644
index 0000000..289e3ee
--- /dev/null
+++ b/pkgs/test/image/test1.gif
Binary files differ
diff --git a/pkgs/test/image/test2.gif b/pkgs/test/image/test2.gif
new file mode 100644
index 0000000..7bae0fc
--- /dev/null
+++ b/pkgs/test/image/test2.gif
Binary files differ
diff --git a/pkgs/test/lib/bootstrap/browser.dart b/pkgs/test/lib/bootstrap/browser.dart
new file mode 100644
index 0000000..b70cf11
--- /dev/null
+++ b/pkgs/test/lib/bootstrap/browser.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2018, 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.
+
+export '../src/bootstrap/browser.dart';
diff --git a/pkgs/test/lib/bootstrap/node.dart b/pkgs/test/lib/bootstrap/node.dart
new file mode 100644
index 0000000..0214d6b
--- /dev/null
+++ b/pkgs/test/lib/bootstrap/node.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2018, 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.
+
+export '../src/bootstrap/node.dart';
diff --git a/pkgs/test/lib/bootstrap/vm.dart b/pkgs/test/lib/bootstrap/vm.dart
new file mode 100644
index 0000000..8115736
--- /dev/null
+++ b/pkgs/test/lib/bootstrap/vm.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2018, 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.
+
+export 'package:test_core/src/bootstrap/vm.dart';
diff --git a/pkgs/test/lib/dart.js b/pkgs/test/lib/dart.js
new file mode 100644
index 0000000..3840ba7
--- /dev/null
+++ b/pkgs/test/lib/dart.js
@@ -0,0 +1,80 @@
+// Copyright (c) 2015, 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.
+
+// This script runs in HTML files and loads the corresponding test scripts for
+// a JS browser. It's used by "pub serve" and user-authored HTML files;
+window.onload = function() {
+
+// Sends an error message to the server indicating that the script failed to
+// load.
+//
+// This mimics a MultiChannel-formatted message.
+var sendLoadException = function(message) {
+ window.parent.postMessage({
+ "data": [0, {"type": "loadException", "message": message}],
+ "exception": true,
+ }, window.location.origin);
+}
+
+// Listen for dartLoadException events and forward to the server.
+window.addEventListener('dartLoadException', function(e) {
+ sendLoadException(e.detail);
+});
+
+// The basename of the current page.
+var name = window.location.href.replace(/.*\//, '').replace(/#.*/, '');
+
+// Find <link rel="x-dart-test">.
+var links = document.getElementsByTagName("link");
+var testLinks = [];
+var length = links.length;
+for (var i = 0; i < length; ++i) {
+ if (links[i].rel == "x-dart-test") testLinks.push(links[i]);
+}
+
+if (testLinks.length != 1) {
+ sendLoadException(
+ 'Expected exactly 1 <link rel="x-dart-test"> in ' + name + ', found ' +
+ testLinks.length + '.');
+ return;
+}
+
+var link = testLinks[0];
+
+if (link.href == '') {
+ sendLoadException(
+ 'Expected <link rel="x-dart-test"> in ' + name + ' to have an "href" ' +
+ 'attribute.');
+ return;
+}
+
+var script = document.createElement('script');
+
+if (typeof trustedTypes !== 'undefined') {
+ const sanitizer = trustedTypes.createPolicy('dart#test', {
+ createScriptURL: (input) => input + '.browser_test.dart.js'
+ });
+ script.src = sanitizer.createScriptURL(link.href);
+} else {
+ script.src = link.href + '.browser_test.dart.js';
+}
+
+script.onerror = function(event) {
+ var message = "Failed to load script at " + script.src +
+ (event.message ? ": " + event.message : ".");
+ sendLoadException(message);
+};
+
+Array.from(document.querySelectorAll('script')).some(currentScript => {
+ if (currentScript.nonce) {
+ script.nonce = currentScript.nonce;
+ return true;
+ }
+});
+
+var parent = link.parentNode;
+document.currentScript = script;
+parent.replaceChild(script, link);
+
+};
diff --git a/pkgs/test/lib/expect.dart b/pkgs/test/lib/expect.dart
new file mode 100644
index 0000000..5e62622
--- /dev/null
+++ b/pkgs/test/lib/expect.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2021, 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.
+
+export 'package:matcher/expect.dart';
diff --git a/pkgs/test/lib/fake.dart b/pkgs/test/lib/fake.dart
new file mode 100644
index 0000000..fc28bef
--- /dev/null
+++ b/pkgs/test/lib/fake.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2020, 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.
+
+// Note: eventually we would like to fold this into test.dart, but we can't do
+// so until Mockito stops implementing its own version of `Fake`, because there
+// is code in the wild that imports both test_api.dart and Mockito.
+
+// ignore: deprecated_member_use
+export 'package:test_api/fake.dart';
diff --git a/pkgs/test/lib/scaffolding.dart b/pkgs/test/lib/scaffolding.dart
new file mode 100644
index 0000000..06e9029
--- /dev/null
+++ b/pkgs/test/lib/scaffolding.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2021, 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.
+
+export 'package:test_core/src/scaffolding.dart';
diff --git a/pkgs/test/lib/src/bootstrap/browser.dart b/pkgs/test/lib/src/bootstrap/browser.dart
new file mode 100644
index 0000000..1a66e2b
--- /dev/null
+++ b/pkgs/test/lib/src/bootstrap/browser.dart
@@ -0,0 +1,21 @@
+// 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.
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_core/src/runner/plugin/remote_platform_helpers.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+
+import '../runner/browser/post_message_channel.dart';
+
+/// Bootstraps a browser test to communicate with the test runner.
+void internalBootstrapBrowserTest(Function Function() getMain,
+ {StreamChannel<Object?>? testChannel}) {
+ var channel = serializeSuite(getMain, hidePrints: false,
+ beforeLoad: (suiteChannel) async {
+ var serialized = await suiteChannel('test.browser.mapper').stream.first;
+ if (serialized is! Map) return;
+ setStackTraceMapper(JSStackTraceMapper.deserialize(serialized)!);
+ });
+ (testChannel ?? postMessageChannel()).pipe(channel);
+}
diff --git a/pkgs/test/lib/src/bootstrap/node.dart b/pkgs/test/lib/src/bootstrap/node.dart
new file mode 100644
index 0000000..21089a4
--- /dev/null
+++ b/pkgs/test/lib/src/bootstrap/node.dart
@@ -0,0 +1,18 @@
+// 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.
+
+import 'package:test_core/src/runner/plugin/remote_platform_helpers.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+
+import '../runner/node/socket_channel.dart';
+
+/// Bootstraps a browser test to communicate with the test runner.
+void internalBootstrapNodeTest(Function Function() getMain) {
+ var channel = serializeSuite(getMain, beforeLoad: (suiteChannel) async {
+ var serialized = await suiteChannel('test.node.mapper').stream.first;
+ if (serialized is! Map) return;
+ setStackTraceMapper(JSStackTraceMapper.deserialize(serialized)!);
+ });
+ socketChannel().then((socket) => socket.pipe(channel));
+}
diff --git a/pkgs/test/lib/src/executable.dart b/pkgs/test/lib/src/executable.dart
new file mode 100644
index 0000000..5780694
--- /dev/null
+++ b/pkgs/test/lib/src/executable.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2015, 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 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+// ignore: implementation_imports
+import 'package:test_core/src/executable.dart' as executable;
+import 'package:test_core/src/runner/hack_register_platform.dart'; // ignore: implementation_imports
+
+import 'runner/browser/platform.dart';
+import 'runner/node/platform.dart';
+
+Future<void> main(List<String> args) async {
+ registerPlatformPlugin([Runtime.nodeJS], NodePlatform.new);
+ registerPlatformPlugin([
+ Runtime.chrome,
+ Runtime.edge,
+ Runtime.firefox,
+ Runtime.safari,
+ ], BrowserPlatform.start);
+
+ await executable.main(args);
+}
diff --git a/pkgs/test/lib/src/runner/browser/browser.dart b/pkgs/test/lib/src/runner/browser/browser.dart
new file mode 100644
index 0000000..b025ea2
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/browser.dart
@@ -0,0 +1,138 @@
+// Copyright (c) 2015, 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:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/errors.dart'; // ignore: implementation_imports
+
+/// An interface for running browser instances.
+///
+/// This is intentionally coarse-grained: browsers are controlled primary from
+/// inside a single tab. Thus this interface only provides support for closing
+/// the browser and seeing if it closes itself.
+///
+/// Any errors starting or running the browser process are reported through
+/// [onExit].
+abstract class Browser {
+ String get name;
+
+ /// The remote debugger URL for this browser.
+ ///
+ /// This will complete to `null` for browsers that don't support remote
+ /// debugging, or if the remote debugging URL can't be found.
+ Future<Uri?> get remoteDebuggerUrl async => null;
+
+ /// The underlying process.
+ ///
+ /// This will fire once the process has started successfully.
+ Future<Process> get _process => _processCompleter.future;
+ final _processCompleter = Completer<Process>();
+
+ /// Whether [close] has been called.
+ var _closed = false;
+
+ /// A future that completes when the browser exits.
+ ///
+ /// If there's a problem starting or running the browser, this will complete
+ /// with an error.
+ Future<void> get onExit => _onExitCompleter.future;
+ final _onExitCompleter = Completer<void>();
+
+ /// Standard IO streams for the underlying browser process.
+ final _ioSubscriptions = <StreamSubscription<String>>[];
+
+ final output = <String>[];
+
+ /// Creates a new browser.
+ ///
+ /// This is intended to be called by subclasses. They pass in [startBrowser],
+ /// which asynchronously returns the browser process. Any errors in
+ /// [startBrowser] (even those raised asynchronously after it returns) are
+ /// piped to [onExit] and will cause the browser to be killed.
+ Browser(Future<Process> Function() startBrowser) {
+ // Don't return a Future here because there's no need for the caller to wait
+ // for the process to actually start. They should just wait for the HTTP
+ // request instead.
+ runZonedGuarded(() async {
+ var process = await startBrowser();
+ _processCompleter.complete(process);
+
+ void drainOutput(Stream<List<int>> stream) {
+ try {
+ _ioSubscriptions.add(stream
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen(output.add, cancelOnError: true));
+ } on StateError catch (_) {}
+ }
+
+ // If we don't drain the stdout and stderr the process can hang.
+ drainOutput(process.stdout);
+ drainOutput(process.stderr);
+
+ var exitCode = await process.exitCode;
+
+ // This hack dodges an otherwise intractable race condition. When the user
+ // presses Control-C, the signal is sent to the browser and the test
+ // runner at the same time. It's possible for the browser to exit before
+ // the [Browser.close] is called, which would trigger the error below.
+ //
+ // A negative exit code signals that the process exited due to a signal.
+ // However, it's possible that this signal didn't come from the user's
+ // Control-C, in which case we do want to throw the error. The only way to
+ // resolve the ambiguity is to wait a brief amount of time and see if this
+ // browser is actually closed.
+ if (!_closed && exitCode < 0) {
+ await Future<void>.delayed(const Duration(milliseconds: 200));
+ }
+
+ if (!_closed && exitCode != 0) {
+ var outputString = output.join('\n');
+ var message = '$name failed with exit code $exitCode.';
+ if (outputString.isNotEmpty) {
+ message += '\nStandard output:\n$outputString';
+ }
+
+ throw ApplicationException(message);
+ }
+
+ _onExitCompleter.complete();
+ }, (error, stackTrace) {
+ // Ignore any errors after the browser has been closed.
+ if (_closed) return;
+
+ // Make sure the process dies even if the error wasn't fatal.
+ _process.then((process) => process.kill());
+
+ if (_onExitCompleter.isCompleted) return;
+ _onExitCompleter.completeError(
+ ApplicationException(
+ 'Failed to run $name: ${getErrorMessage(error)}.'),
+ stackTrace);
+ });
+ }
+
+ /// Kills the browser process.
+ ///
+ /// Returns the same [Future] as [onExit], except that it won't emit
+ /// exceptions.
+ Future<void> close() async {
+ _closed = true;
+
+ // If we don't manually close the stream the test runner can hang.
+ // For example this happens with Chrome Headless.
+ // See SDK issue: https://github.com/dart-lang/sdk/issues/31264
+ for (var stream in _ioSubscriptions) {
+ unawaited(stream.cancel());
+ }
+
+ (await _process).kill();
+
+ // Swallow exceptions. The user should explicitly use [onExit] for these.
+ return onExit.onError((_, __) {});
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/browser_manager.dart b/pkgs/test/lib/src/runner/browser/browser_manager.dart
new file mode 100644
index 0000000..409fbb4
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/browser_manager.dart
@@ -0,0 +1,359 @@
+// Copyright (c) 2015, 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 'package:async/async.dart';
+import 'package:pool/pool.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart' show Compiler, Runtime, StackTraceMapper;
+import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/load_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import '../executable_settings.dart';
+import 'browser.dart';
+import 'chrome.dart';
+import 'firefox.dart';
+import 'microsoft_edge.dart';
+import 'safari.dart';
+
+/// A class that manages the connection to a single running browser.
+///
+/// This is in charge of telling the browser which test suites to load and
+/// converting its responses into [Suite] objects.
+class BrowserManager {
+ /// The browser instance that this is connected to via [_channel].
+ final Browser _browser;
+
+ // TODO(nweiz): Consider removing the duplication between this and
+ // [_browser.name].
+ /// The [Runtime] for [_browser].
+ final Runtime _runtime;
+
+ /// The channel used to communicate with the browser.
+ ///
+ /// This is connected to a page running `static/host.dart`.
+ late final MultiChannel<Object> _channel;
+
+ /// A pool that ensures that limits the number of initial connections the
+ /// manager will wait for at once.
+ ///
+ /// This isn't the *total* number of connections; any number of iframes may be
+ /// loaded in the same browser. However, the browser can only load so many at
+ /// once, and we want a timeout in case they fail so we only wait for so many
+ /// at once.
+ final _pool = Pool(8);
+
+ /// The ID of the next suite to be loaded.
+ ///
+ /// This is used to ensure that the suites can be referred to consistently
+ /// across the client and server.
+ int _suiteID = 0;
+
+ /// Whether the channel to the browser has closed.
+ bool _closed = false;
+
+ /// The completer for [_BrowserEnvironment.displayPause].
+ ///
+ /// This will be `null` as long as the browser isn't displaying a pause
+ /// screen.
+ CancelableCompleter<void>? _pauseCompleter;
+
+ /// The controller for [_BrowserEnvironment.onRestart].
+ final _onRestartController = StreamController<void>.broadcast();
+
+ /// The environment to attach to each suite.
+ late final Future<_BrowserEnvironment> _environment;
+
+ /// Controllers for every suite in this browser.
+ ///
+ /// These are used to mark suites as debugging or not based on the browser's
+ /// pings.
+ final _controllers = <RunnerSuiteController>{};
+
+ // A timer that's reset whenever we receive a message from the browser.
+ //
+ // Because the browser stops running code when the user is actively debugging,
+ // this lets us detect whether they're debugging reasonably accurately.
+ late final RestartableTimer _timer;
+
+ /// Starts the browser identified by [runtime] and has it connect to [url].
+ ///
+ /// [url] should serve a page that establishes a WebSocket connection with
+ /// this process. That connection, once established, should be emitted via
+ /// [future]. If [debug] is true, starts the browser in debug mode, with its
+ /// debugger interfaces on and detected.
+ ///
+ /// The [settings] indicate how to invoke this browser's executable.
+ ///
+ /// Returns the browser manager, or throws an [ApplicationException] if a
+ /// connection fails to be established.
+ static Future<BrowserManager> start(
+ Runtime runtime,
+ Uri url,
+ Future<WebSocketChannel> future,
+ ExecutableSettings settings,
+ Configuration configuration) =>
+ _start(runtime, url, future, settings, configuration, 1);
+
+ static const _maxRetries = 3;
+ static Future<BrowserManager> _start(
+ Runtime runtime,
+ Uri url,
+ Future<WebSocketChannel> future,
+ ExecutableSettings settings,
+ Configuration configuration,
+ int attempt) {
+ var browser = _newBrowser(url, runtime, settings, configuration);
+
+ var completer = Completer<BrowserManager>();
+
+ // TODO(nweiz): Gracefully handle the browser being killed before the
+ // tests complete.
+ browser.onExit
+ .then<void>((_) => throw ApplicationException(
+ '${runtime.name} exited before connecting.'))
+ .onError<Object>((error, stackTrace) {
+ if (!completer.isCompleted) {
+ completer.completeError(error, stackTrace);
+ }
+ });
+
+ future.then((webSocket) {
+ if (completer.isCompleted) return;
+ completer.complete(BrowserManager._(browser, runtime, webSocket));
+ }).onError((Object error, StackTrace stackTrace) {
+ browser.close();
+ if (completer.isCompleted) return;
+ completer.completeError(error, stackTrace);
+ });
+
+ return completer.future.timeout(const Duration(seconds: 30), onTimeout: () {
+ browser.close();
+ if (attempt >= _maxRetries) {
+ throw ApplicationException(
+ 'Timed out waiting for ${runtime.name} to connect.\n'
+ 'Browser output: ${browser.output.join('\n')}');
+ }
+ return _start(runtime, url, future, settings, configuration, ++attempt);
+ });
+ }
+
+ /// Starts the browser identified by [browser] using [settings] and has it load [url].
+ ///
+ /// If [debug] is true, starts the browser in debug mode.
+ static Browser _newBrowser(Uri url, Runtime browser,
+ ExecutableSettings settings, Configuration configuration) =>
+ switch (browser.root) {
+ Runtime.chrome => Chrome(url, configuration, settings: settings),
+ Runtime.firefox => Firefox(url, settings: settings),
+ Runtime.safari => Safari(url, settings: settings),
+ Runtime.edge => MicrosoftEdge(url, configuration, settings: settings),
+ _ => throw ArgumentError('$browser is not a browser.'),
+ };
+
+ /// Creates a new BrowserManager that communicates with [browser] over
+ /// [webSocket].
+ BrowserManager._(this._browser, this._runtime, WebSocketChannel webSocket) {
+ // The duration should be short enough that the debugging console is open as
+ // soon as the user is done setting breakpoints, but long enough that a test
+ // doing a lot of synchronous work doesn't trigger a false positive.
+ //
+ // Start this canceled because we don't want it to start ticking until we
+ // get some response from the iframe.
+ _timer = RestartableTimer(const Duration(seconds: 3), () {
+ for (var controller in _controllers) {
+ controller.setDebugging(true);
+ }
+ })
+ ..cancel();
+
+ // Whenever we get a message, no matter which child channel it's for, we the
+ // know browser is still running code which means the user isn't debugging.
+ _channel = MultiChannel(
+ webSocket.cast<String>().transform(jsonDocument).changeStream((stream) {
+ return stream.map((message) {
+ if (!_closed) _timer.reset();
+ for (var controller in _controllers) {
+ controller.setDebugging(false);
+ }
+
+ return message;
+ });
+ }));
+
+ _environment = _loadBrowserEnvironment();
+ _channel.stream.listen(
+ (message) => _onMessage(message as Map<Object, Object?>),
+ onDone: close);
+ }
+
+ /// Loads [_BrowserEnvironment].
+ Future<_BrowserEnvironment> _loadBrowserEnvironment() async =>
+ _BrowserEnvironment(
+ this, await _browser.remoteDebuggerUrl, _onRestartController.stream);
+
+ /// Tells the browser the load a test suite from the URL [url].
+ ///
+ /// [url] should be an HTML page with a reference to the JS-compiled test
+ /// suite. [path] is the path of the original test suite file, which is used
+ /// for reporting. [suiteConfig] is the configuration for the test suite.
+ ///
+ /// If [mapper] is passed, it's used to map stack traces for errors coming
+ /// from this test suite.
+ Future<RunnerSuite> load(String path, Uri url, SuiteConfiguration suiteConfig,
+ Map<String, Object?> message, Compiler compiler,
+ {StackTraceMapper? mapper, Duration? timeout}) async {
+ url = url.replace(
+ fragment: Uri.encodeFull(jsonEncode({
+ 'metadata': suiteConfig.metadata.serialize(),
+ 'browser': _runtime.identifier,
+ 'compiler': compiler.serialize(),
+ })));
+
+ var suiteID = _suiteID++;
+ RunnerSuiteController? controller;
+ void closeIframe() {
+ if (_closed) return;
+ if (controller != null) _controllers.remove(controller);
+ _channel.sink.add({'command': 'closeSuite', 'id': suiteID});
+ }
+
+ // The virtual channel will be closed when the suite is closed, in which
+ // case we should unload the iframe.
+ var virtualChannel = _channel.virtualChannel();
+ var suiteChannelID = virtualChannel.id;
+ var suiteChannel = virtualChannel
+ .transformStream(StreamTransformer.fromHandlers(handleDone: (sink) {
+ closeIframe();
+ sink.close();
+ }));
+
+ var suite = _pool.withResource<RunnerSuite>(() async {
+ _channel.sink.add({
+ 'command': 'loadSuite',
+ 'url': url.toString(),
+ 'id': suiteID,
+ 'channel': suiteChannelID
+ });
+
+ try {
+ controller = deserializeSuite(
+ path,
+ currentPlatform(_runtime, compiler),
+ suiteConfig,
+ await _environment,
+ suiteChannel.cast(),
+ message, gatherCoverage: () async {
+ var browser = _browser;
+ if (browser is Chrome) return browser.gatherCoverage();
+ return {};
+ });
+
+ controller!
+ .channel('test.browser.mapper')
+ .sink
+ .add(mapper?.serialize());
+
+ _controllers.add(controller!);
+ return await controller!.suite;
+ } catch (_) {
+ closeIframe();
+ rethrow;
+ }
+ });
+ if (timeout != null) {
+ suite = suite.timeout(timeout, onTimeout: () {
+ throw LoadException(
+ path,
+ 'Timed out waiting for browser to load test suite. '
+ 'Browser output: ${_browser.output.join('\n')}');
+ });
+ }
+ return suite;
+ }
+
+ /// An implementation of [Environment.displayPause].
+ CancelableOperation<void> _displayPause() {
+ if (_pauseCompleter != null) return _pauseCompleter!.operation;
+
+ final pauseCompleter = _pauseCompleter = CancelableCompleter(onCancel: () {
+ _channel.sink.add({'command': 'resume'});
+ _pauseCompleter = null;
+ });
+
+ pauseCompleter.operation.value.whenComplete(() {
+ _pauseCompleter = null;
+ });
+
+ _channel.sink.add({'command': 'displayPause'});
+
+ return pauseCompleter.operation;
+ }
+
+ /// The callback for handling messages received from the host page.
+ void _onMessage(Map<Object, Object?> message) {
+ switch (message['command'] as String) {
+ case 'ping':
+ break;
+
+ case 'restart':
+ _onRestartController.add(null);
+ break;
+
+ case 'resume':
+ _pauseCompleter?.complete();
+ break;
+
+ default:
+ // Unreachable.
+ assert(false);
+ break;
+ }
+ }
+
+ /// Closes the manager and releases any resources it owns, including closing
+ /// the browser.
+ Future<void> close() => _closeMemoizer.runOnce(() {
+ _closed = true;
+ _timer.cancel();
+ _pauseCompleter?.complete();
+ _pauseCompleter = null;
+ _controllers.clear();
+ return _browser.close();
+ });
+ final _closeMemoizer = AsyncMemoizer<void>();
+}
+
+/// An implementation of [Environment] for the browser.
+///
+/// All methods forward directly to [BrowserManager].
+class _BrowserEnvironment implements Environment {
+ final BrowserManager _manager;
+
+ @override
+ final supportsDebugging = true;
+
+ @override
+ Null get observatoryUrl => null;
+
+ @override
+ final Uri? remoteDebuggerUrl;
+
+ @override
+ final Stream<void> onRestart;
+
+ _BrowserEnvironment(this._manager, this.remoteDebuggerUrl, this.onRestart);
+
+ @override
+ CancelableOperation<void> displayPause() => _manager._displayPause();
+}
diff --git a/pkgs/test/lib/src/runner/browser/chrome.dart b/pkgs/test/lib/src/runner/browser/chrome.dart
new file mode 100644
index 0000000..e7dff75
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/chrome.dart
@@ -0,0 +1,194 @@
+// Copyright (c) 2015, 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:collection/collection.dart';
+import 'package:coverage/coverage.dart';
+import 'package:path/path.dart' as p;
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+
+import '../executable_settings.dart';
+import 'browser.dart';
+import 'chromium.dart';
+import 'default_settings.dart';
+
+/// A class for running an instance of Chrome.
+///
+/// Most of the communication with the browser is expected to happen via HTTP,
+/// so this exposes a bare-bones API. The browser starts as soon as the class is
+/// constructed, and is killed when [close] is called.
+///
+/// Any errors starting or running the process are reported through [onExit].
+class Chrome extends Browser {
+ @override
+ final name = 'Chrome';
+
+ @override
+ final Future<Uri?> remoteDebuggerUrl;
+
+ final Future<WipConnection> _tabConnection;
+ final Map<String, String> _idToUrl;
+
+ /// Starts a new instance of Chrome open to the given [url], which may be a
+ /// [Uri] or a [String].
+ factory Chrome(Uri url, Configuration configuration,
+ {ExecutableSettings? settings}) {
+ settings ??= defaultSettings[Runtime.chrome]!;
+ var remoteDebuggerCompleter = Completer<Uri?>.sync();
+ var connectionCompleter = Completer<WipConnection>();
+ var idToUrl = <String, String>{};
+ return Chrome._(() async {
+ Future<Process> tryPort([int? port]) async {
+ var process = await ChromiumBasedBrowser.chrome.spawn(
+ url,
+ configuration,
+ settings: settings,
+ additionalArgs: [
+ if (port != null)
+ // Chrome doesn't provide any way of ensuring that this port was
+ // successfully bound. It produces an error if the binding fails,
+ // but without a reliable and fast way to tell if it succeeded
+ // that doesn't provide us much. It's very unlikely that this port
+ // will fail, though.
+ '--remote-debugging-port=$port',
+ ],
+ );
+
+ if (port != null) {
+ remoteDebuggerCompleter.complete(
+ getRemoteDebuggerUrl(Uri.parse('http://localhost:$port')));
+
+ connectionCompleter.complete(_connect(process, port, idToUrl, url));
+ } else {
+ remoteDebuggerCompleter.complete(null);
+ }
+
+ return process;
+ }
+
+ if (!configuration.debug) return tryPort();
+ return getUnusedPort<Process>(tryPort);
+ }, remoteDebuggerCompleter.future, connectionCompleter.future, idToUrl);
+ }
+
+ /// Returns a Dart based hit-map containing coverage report, suitable for use
+ /// with `package:coverage`.
+ Future<Map<String, dynamic>> gatherCoverage() async {
+ var tabConnection = await _tabConnection;
+ var response = await tabConnection.debugger.connection
+ .sendCommand('Profiler.takePreciseCoverage', {});
+ var result =
+ (response.result!['result'] as List).cast<Map<String, dynamic>>();
+ var httpClient = HttpClient();
+ var coverage = await parseChromeCoverage(
+ result,
+ (scriptId) => _sourceProvider(scriptId, httpClient),
+ (scriptId) => _sourceMapProvider(scriptId, httpClient),
+ _sourceUriProvider,
+ );
+ httpClient.close(force: true);
+ return coverage;
+ }
+
+ Chrome._(super.startBrowser, this.remoteDebuggerUrl, this._tabConnection,
+ this._idToUrl);
+
+ Future<Uri?> _sourceUriProvider(String sourceUrl, String scriptId) async {
+ var script = _idToUrl[scriptId];
+ if (script == null) return null;
+ var sourceUri = Uri.parse(sourceUrl);
+ if (sourceUri.scheme == 'file') return sourceUri;
+ // If the provided sourceUrl is relative, determine the package path.
+ var uri = Uri.parse(script);
+ var path = p.join(
+ p.joinAll(uri.pathSegments.sublist(1, uri.pathSegments.length - 1)),
+ sourceUrl);
+ return path.contains('/packages/')
+ ? Uri(scheme: 'package', path: path.split('/packages/').last)
+ : null;
+ }
+
+ Future<String?> _sourceMapProvider(
+ String scriptId, HttpClient httpClient) async {
+ var script = _idToUrl[scriptId];
+ if (script == null) return null;
+ return await httpClient.getString('$script.map');
+ }
+
+ Future<String?> _sourceProvider(
+ String scriptId, HttpClient httpClient) async {
+ var script = _idToUrl[scriptId];
+ if (script == null) return null;
+ return await httpClient.getString(script);
+ }
+}
+
+Future<WipConnection> _connect(
+ Process process, int port, Map<String, String> idToUrl, Uri url) async {
+ // Wait for Chrome to be in a ready state.
+ await process.stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .firstWhere((line) => line.startsWith('DevTools listening'));
+
+ var chromeConnection = ChromeConnection('localhost', port);
+ ChromeTab? tab;
+ var attempt = 0;
+ while (tab == null) {
+ attempt++;
+ var tabs = await chromeConnection.getTabs();
+ tab = tabs.firstWhereOrNull((tab) => tab.url == url.toString());
+ if (tab == null) {
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+ if (attempt > 5) {
+ throw StateError('Could not connect to test tab with url: $url');
+ }
+ }
+ }
+ var tabConnection = await tab.connect();
+
+ // Enable debugging.
+ await tabConnection.debugger.enable();
+
+ // Coverage reports are in terms of scriptIds so keep note of URLs.
+ tabConnection.debugger.onScriptParsed.listen((data) {
+ var script = data.script;
+ if (script.url.isNotEmpty) idToUrl[script.scriptId] = script.url;
+ });
+
+ // Enable coverage collection.
+ await tabConnection.debugger.connection.sendCommand('Profiler.enable', {});
+ await tabConnection.debugger.connection.sendCommand(
+ 'Profiler.startPreciseCoverage', {'detailed': true, 'callCount': false});
+
+ return tabConnection;
+}
+
+extension on HttpClient {
+ Encoding determineEncoding(HttpHeaders headers) {
+ final contentType = headers.contentType?.charset;
+
+ /// Using the `charset` property of the `contentType` if available.
+ /// If it's unavailable or if the encoding name is unknown, [latin1] is used by default,
+ /// as per [RFC 2616][].
+ ///
+ /// [RFC 2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
+ return Encoding.getByName(contentType) ?? latin1;
+ }
+
+ Future<String?> getString(String url) async {
+ final request = await getUrl(Uri.parse(url));
+ final response = await request.close();
+ if (response.statusCode != HttpStatus.ok) return null;
+ var bytes = [await for (var chunk in response) ...chunk];
+ final encoding = determineEncoding(response.headers);
+ return encoding.decode(bytes);
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/chromium.dart b/pkgs/test/lib/src/runner/browser/chromium.dart
new file mode 100644
index 0000000..b0243ba
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/chromium.dart
@@ -0,0 +1,63 @@
+// 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:io';
+
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+
+import '../executable_settings.dart';
+import 'default_settings.dart';
+
+enum ChromiumBasedBrowser {
+ chrome(Runtime.chrome),
+ microsoftEdge(Runtime.edge);
+
+ final Runtime runtime;
+
+ const ChromiumBasedBrowser(this.runtime);
+
+ Future<Process> spawn(
+ Uri url,
+ Configuration configuration, {
+ ExecutableSettings? settings,
+ List<String> additionalArgs = const [],
+ }) async {
+ settings ??= defaultSettings[runtime];
+
+ var dir = createTempDir();
+ var args = [
+ '--user-data-dir=$dir',
+ url.toString(),
+ '--enable-logging=stderr',
+ '--v=0',
+ '--disable-extensions',
+ '--disable-popup-blocking',
+ '--bwsi',
+ '--no-first-run',
+ '--no-default-browser-check',
+ '--disable-default-apps',
+ '--disable-translate',
+ '--disable-dev-shm-usage',
+ if (settings!.headless && !configuration.pauseAfterLoad) ...[
+ '--headless',
+ '--disable-gpu',
+ ],
+ if (!configuration.debug)
+ // We don't actually connect to the remote debugger, but Chrome will
+ // close as soon as the page is loaded if we don't turn it on.
+ '--remote-debugging-port=0',
+ ...settings.arguments,
+ ...additionalArgs,
+ ];
+
+ var process = await Process.start(settings.executable, args);
+
+ unawaited(process.exitCode.then((_) => Directory(dir).deleteWithRetry()));
+
+ return process;
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/compilers/compiler_support.dart b/pkgs/test/lib/src/runner/browser/compilers/compiler_support.dart
new file mode 100644
index 0000000..4fa1b88
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/compilers/compiler_support.dart
@@ -0,0 +1,105 @@
+// 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.');
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/compilers/dart2js.dart b/pkgs/test/lib/src/runner/browser/compilers/dart2js.dart
new file mode 100644
index 0000000..3cd6745
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/compilers/dart2js.dart
@@ -0,0 +1,186 @@
+// 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:io';
+
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
+import 'package:shelf_packages_handler/shelf_packages_handler.dart';
+import 'package:shelf_static/shelf_static.dart';
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+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/dart2js_compiler_pool.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/package_version.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/package_config.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import '../../../util/math.dart';
+import '../../../util/one_off_handler.dart';
+import '../../../util/package_map.dart';
+import '../../../util/path_handler.dart';
+import 'compiler_support.dart';
+
+/// Support for Dart2Js compiled tests.
+class Dart2JsSupport extends CompilerSupport with JsHtmlWrapper {
+ /// Whether [close] has been called.
+ bool _closed = false;
+
+ /// The temporary directory in which compiled JS is emitted.
+ final _compiledDir = createTempDir();
+
+ /// A map from test suite paths to Futures that will complete once those
+ /// suites are finished compiling.
+ ///
+ /// This is used to make sure that a given test suite is only compiled once
+ /// per run, rather than once per browser per run.
+ final _compileFutures = <String, Future<void>>{};
+
+ /// The [Dart2JsCompilerPool] managing active instances of `dart2js`.
+ final _compilerPool = Dart2JsCompilerPool();
+
+ /// Mappers for Dartifying stack traces, indexed by test path.
+ final _mappers = <String, StackTraceMapper>{};
+
+ /// A [PathHandler] used to serve test specific artifacts.
+ final _pathHandler = PathHandler();
+
+ /// The root directory served statically by this server.
+ final String _root;
+
+ /// Each compiler serves its tests under a different randomly-generated
+ /// secret URI to ensure that other users on the same system can't snoop
+ /// on data being served through this server, as well as distinguish tests
+ /// from different compilers from each other.
+ final String _secret = randomUrlSecret();
+
+ /// The underlying server.
+ final shelf.Server _server;
+
+ /// A [OneOffHandler] for servicing WebSocket connections for
+ /// [BrowserManager]s.
+ ///
+ /// This is one-off because each [BrowserManager] can only connect to a single
+ /// WebSocket.
+ final _webSocketHandler = OneOffHandler();
+
+ @override
+ Uri get serverUrl => _server.url.resolve('$_secret/');
+
+ Dart2JsSupport._(super.config, super.defaultTemplatePath, this._server,
+ this._root, String faviconPath) {
+ var cascade = shelf.Cascade()
+ .add(_webSocketHandler.handler)
+ .add(packagesDirHandler())
+ .add(_pathHandler.handler)
+ .add(createStaticHandler(_root))
+ .add(htmlWrapperHandler);
+
+ var pipeline = const shelf.Pipeline()
+ .addMiddleware(PathHandler.nestedIn(_secret))
+ .addHandler(cascade.handler);
+
+ _server.mount(shelf.Cascade()
+ .add(createFileHandler(faviconPath))
+ .add(pipeline)
+ .handler);
+ }
+
+ static Future<Dart2JsSupport> start({
+ required Configuration config,
+ required String defaultTemplatePath,
+ required String root,
+ required String faviconPath,
+ }) async {
+ var server = shelf_io.IOServer(await HttpMultiServer.loopback(0));
+ return Dart2JsSupport._(
+ config, defaultTemplatePath, server, root, faviconPath);
+ }
+
+ @override
+ Future<void> compileSuite(
+ String dartPath, SuiteConfiguration suiteConfig, SuitePlatform platform) {
+ return _compileFutures.putIfAbsent(dartPath, () async {
+ var dir = Directory(_compiledDir).createTempSync('test_').path;
+ var jsPath = p.join(dir, '${p.basename(dartPath)}.browser_test.dart.js');
+ var bootstrapContent = '''
+ ${suiteConfig.metadata.languageVersionComment ?? await rootPackageLanguageVersionComment}
+ import 'package:test/src/bootstrap/browser.dart';
+ import 'package:test/src/runner/browser/dom.dart' as dom;
+
+ import '${await absoluteUri(dartPath)}' as test;
+
+ void main() {
+ dom.window.console.log(r'Startup for test path $dartPath');
+ internalBootstrapBrowserTest(() => test.main);
+ }
+ ''';
+
+ await _compilerPool.compile(bootstrapContent, jsPath, suiteConfig);
+ if (_closed) return;
+
+ var bootstrapUrl = '${p.toUri(p.relative(dartPath, from: _root)).path}'
+ '.browser_test.dart';
+ _pathHandler.add(bootstrapUrl, (request) {
+ return shelf.Response.ok(bootstrapContent,
+ headers: {'Content-Type': 'application/dart'});
+ });
+
+ var jsUrl = '${p.toUri(p.relative(dartPath, from: _root)).path}'
+ '.browser_test.dart.js';
+ _pathHandler.add(jsUrl, (request) {
+ return shelf.Response.ok(File(jsPath).readAsStringSync(),
+ headers: {'Content-Type': 'application/javascript'});
+ });
+
+ var mapUrl = '${p.toUri(p.relative(dartPath, from: _root)).path}'
+ '.browser_test.dart.js.map';
+ _pathHandler.add(mapUrl, (request) {
+ return shelf.Response.ok(File('$jsPath.map').readAsStringSync(),
+ headers: {'Content-Type': 'application/json'});
+ });
+
+ if (suiteConfig.jsTrace) return;
+ var mapPath = '$jsPath.map';
+ _mappers[dartPath] = JSStackTraceMapper(File(mapPath).readAsStringSync(),
+ mapUrl: p.toUri(mapPath),
+ sdkRoot: Uri.parse('org-dartlang-sdk:///sdk'),
+ packageMap: (await currentPackageConfig).toPackageMap());
+ });
+ }
+
+ @override
+ Future<void> close() async {
+ if (_closed) return;
+ _closed = true;
+ await Future.wait([
+ Directory(_compiledDir).deleteWithRetry(),
+ _compilerPool.close(),
+ _server.close(),
+ ]);
+ }
+
+ @override
+ StackTraceMapper? stackTraceMapperForPath(String dartPath) =>
+ _mappers[dartPath];
+
+ @override
+ (Uri, Future<WebSocketChannel>) get webSocket {
+ var completer = Completer<WebSocketChannel>.sync();
+ // Note: the WebSocketChannel type below is needed for compatibility with
+ // package:shelf_web_socket v2.
+ var path =
+ _webSocketHandler.create(webSocketHandler((WebSocketChannel ws, _) {
+ completer.complete(ws);
+ }));
+ var webSocketUrl = serverUrl.replace(scheme: 'ws').resolve(path);
+ return (webSocketUrl, completer.future);
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/compilers/dart2wasm.dart b/pkgs/test/lib/src/runner/browser/compilers/dart2wasm.dart
new file mode 100644
index 0000000..31ad661
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/compilers/dart2wasm.dart
@@ -0,0 +1,192 @@
+// 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:io';
+
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
+import 'package:shelf_packages_handler/shelf_packages_handler.dart';
+import 'package:shelf_static/shelf_static.dart';
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+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/package_version.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/wasm_compiler_pool.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/package_config.dart'; // ignore: implementation_imports
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import '../../../util/math.dart';
+import '../../../util/one_off_handler.dart';
+import '../../../util/path_handler.dart';
+import '../browser_manager.dart';
+import 'compiler_support.dart';
+
+/// Support for Dart2Wasm compiled tests.
+class Dart2WasmSupport extends CompilerSupport with WasmHtmlWrapper {
+ /// Whether [close] has been called.
+ bool _closed = false;
+
+ /// The temporary directory in which compiled JS is emitted.
+ final _compiledDir = createTempDir();
+
+ /// A map from test suite paths to Futures that will complete once those
+ /// suites are finished compiling.
+ ///
+ /// This is used to make sure that a given test suite is only compiled once
+ /// per run, rather than once per browser per run.
+ final _compileFutures = <String, Future<void>>{};
+
+ /// The [WasmCompilerPool] managing active instances of `dart2wasm`.
+ final _compilerPool = WasmCompilerPool();
+
+ /// The `package:test` side wrapper for the Dart2Wasm runtime.
+ final String _jsRuntimeWrapper;
+
+ /// Mappers for Dartifying stack traces, indexed by test path.
+ final _mappers = <String, StackTraceMapper>{};
+
+ /// A [PathHandler] used to serve test specific artifacts.
+ final _pathHandler = PathHandler();
+
+ /// The root directory served statically by this server.
+ final String _root;
+
+ /// Each compiler serves its tests under a different randomly-generated
+ /// secret URI to ensure that other users on the same system can't snoop
+ /// on data being served through this server, as well as distinguish tests
+ /// from different compilers from each other.
+ final String _secret = randomUrlSecret();
+
+ /// The underlying server.
+ final shelf.Server _server;
+
+ /// A [OneOffHandler] for servicing WebSocket connections for
+ /// [BrowserManager]s.
+ ///
+ /// This is one-off because each [BrowserManager] can only connect to a single
+ /// WebSocket.
+ final _webSocketHandler = OneOffHandler();
+
+ @override
+ Uri get serverUrl => _server.url.resolve('$_secret/');
+
+ Dart2WasmSupport._(super.config, super.defaultTemplatePath,
+ this._jsRuntimeWrapper, this._server, this._root, String faviconPath) {
+ var cascade = shelf.Cascade()
+ .add(_webSocketHandler.handler)
+ .add(packagesDirHandler())
+ .add(_pathHandler.handler)
+ .add(createStaticHandler(_root))
+ .add(htmlWrapperHandler);
+
+ var pipeline = const shelf.Pipeline()
+ .addMiddleware(PathHandler.nestedIn(_secret))
+ .addHandler(cascade.handler);
+
+ _server.mount(shelf.Cascade()
+ .add(createFileHandler(faviconPath))
+ .add(pipeline)
+ .handler);
+ }
+
+ static Future<Dart2WasmSupport> start({
+ required Configuration config,
+ required String defaultTemplatePath,
+ required String jsRuntimeWrapper,
+ required String root,
+ required String faviconPath,
+ }) async {
+ var server = shelf_io.IOServer(await HttpMultiServer.loopback(0));
+ return Dart2WasmSupport._(config, defaultTemplatePath, jsRuntimeWrapper,
+ server, root, faviconPath);
+ }
+
+ @override
+ Future<void> compileSuite(
+ String dartPath, SuiteConfiguration suiteConfig, SuitePlatform platform) {
+ return _compileFutures.putIfAbsent(dartPath, () async {
+ var dir = Directory(_compiledDir).createTempSync('test_').path;
+
+ var baseCompiledPath =
+ p.join(dir, '${p.basename(dartPath)}.browser_test.dart');
+ var baseUrl =
+ '${p.toUri(p.relative(dartPath, from: _root)).path}.browser_test.dart';
+ var wasmUrl = '$baseUrl.wasm';
+ var jsRuntimeWrapperUrl = '$baseUrl.js';
+ var jsRuntimeUrl = '$baseUrl.mjs';
+ var htmlUrl = '$baseUrl.html';
+
+ var bootstrapContent = '''
+ ${suiteConfig.metadata.languageVersionComment ?? await rootPackageLanguageVersionComment}
+ import 'package:test/src/bootstrap/browser.dart';
+
+ import '${await absoluteUri(dartPath)}' as test;
+
+ void main() {
+ internalBootstrapBrowserTest(() => test.main);
+ }
+ ''';
+
+ await _compilerPool.compile(
+ bootstrapContent, baseCompiledPath, suiteConfig);
+ if (_closed) return;
+
+ var wasmPath = '$baseCompiledPath.wasm';
+ _pathHandler.add(wasmUrl, (request) {
+ return shelf.Response.ok(File(wasmPath).readAsBytesSync(),
+ headers: {'Content-Type': 'application/wasm'});
+ });
+
+ _pathHandler.add(jsRuntimeWrapperUrl, (request) {
+ return shelf.Response.ok(File(_jsRuntimeWrapper).readAsBytesSync(),
+ headers: {'Content-Type': 'application/javascript'});
+ });
+
+ var jsRuntimePath = '$baseCompiledPath.mjs';
+ _pathHandler.add(jsRuntimeUrl, (request) {
+ return shelf.Response.ok(File(jsRuntimePath).readAsBytesSync(),
+ headers: {'Content-Type': 'application/javascript'});
+ });
+
+ var htmlPath = '$baseCompiledPath.html';
+ _pathHandler.add(htmlUrl, (request) {
+ return shelf.Response.ok(File(htmlPath).readAsBytesSync(),
+ headers: {'Content-Type': 'text/html'});
+ });
+ });
+ }
+
+ @override
+ Future<void> close() async {
+ if (_closed) return;
+ _closed = true;
+ await Future.wait([
+ Directory(_compiledDir).deleteWithRetry(),
+ _compilerPool.close(),
+ _server.close(),
+ ]);
+ }
+
+ @override
+ StackTraceMapper? stackTraceMapperForPath(String dartPath) =>
+ _mappers[dartPath];
+
+ @override
+ (Uri, Future<WebSocketChannel>) get webSocket {
+ var completer = Completer<WebSocketChannel>.sync();
+ // Note: the WebSocketChannel type below is needed for compatibility with
+ // package:shelf_web_socket v2.
+ var path =
+ _webSocketHandler.create(webSocketHandler((WebSocketChannel ws, _) {
+ completer.complete(ws);
+ }));
+ var webSocketUrl = serverUrl.replace(scheme: 'ws').resolve(path);
+ return (webSocketUrl, completer.future);
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/compilers/precompiled.dart b/pkgs/test/lib/src/runner/browser/compilers/precompiled.dart
new file mode 100644
index 0000000..9cf44aa
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/compilers/precompiled.dart
@@ -0,0 +1,149 @@
+// 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:io';
+
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
+import 'package:shelf_packages_handler/shelf_packages_handler.dart';
+import 'package:shelf_static/shelf_static.dart';
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+import 'package:test_api/backend.dart'
+ show Compiler, 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:test_core/src/util/package_config.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import '../../../util/math.dart';
+import '../../../util/one_off_handler.dart';
+import '../../../util/package_map.dart';
+import '../../../util/path_handler.dart';
+import 'compiler_support.dart';
+
+class JsPrecompiledSupport = PrecompiledSupport with JsHtmlWrapper;
+class WasmPrecompiledSupport = PrecompiledSupport with WasmHtmlWrapper;
+
+/// Support for precompiled test files.
+abstract class PrecompiledSupport extends CompilerSupport {
+ /// Whether [close] has been called.
+ bool _closed = false;
+
+ /// Mappers for Dartifying stack traces, indexed by test path.
+ final _mappers = <String, StackTraceMapper>{};
+
+ /// The root directory served statically by the server.
+ final String _root;
+
+ /// Each compiler serves its tests under a different randomly-generated
+ /// secret URI to ensure that other users on the same system can't snoop
+ /// on data being served through this server, as well as distinguish tests
+ /// from different compilers from each other.
+ final String _secret = randomUrlSecret();
+
+ /// The underlying server.
+ final shelf.Server _server;
+
+ /// A [OneOffHandler] for servicing WebSocket connections for
+ /// [BrowserManager]s.
+ ///
+ /// This is one-off because each [BrowserManager] can only connect to a single
+ /// WebSocket.
+ final _webSocketHandler = OneOffHandler();
+
+ /// The URL at which this compiler serves its tests.
+ ///
+ /// Each compiler serves its tests under a different directory.
+ @override
+ Uri get serverUrl => _server.url.resolve('$_secret/');
+
+ PrecompiledSupport._(super.config, super.defaultTemplatePath, this._server,
+ this._root, String faviconPath) {
+ var cascade = shelf.Cascade()
+ .add(_webSocketHandler.handler)
+ .add(createStaticHandler(_root, serveFilesOutsidePath: true))
+ // TODO: This packages dir handler should not be necessary?
+ .add(packagesDirHandler())
+ // Even for precompiled tests, we will auto-create a bootstrap html file
+ // if none was present.
+ .add(htmlWrapperHandler);
+
+ var pipeline = const shelf.Pipeline()
+ .addMiddleware(PathHandler.nestedIn(_secret))
+ .addHandler(cascade.handler);
+
+ _server.mount(shelf.Cascade()
+ .add(createFileHandler(faviconPath))
+ .add(pipeline)
+ .handler);
+ }
+
+ static Future<PrecompiledSupport> start({
+ required Compiler compiler,
+ required Configuration config,
+ required String defaultTemplatePath,
+ required String root,
+ required String faviconPath,
+ }) async {
+ var server = shelf_io.IOServer(await HttpMultiServer.loopback(0));
+
+ return switch (compiler) {
+ Compiler.dart2js => JsPrecompiledSupport._(
+ config, defaultTemplatePath, server, root, faviconPath),
+ Compiler.dart2wasm => WasmPrecompiledSupport._(
+ config, defaultTemplatePath, server, root, faviconPath),
+ Compiler.exe ||
+ Compiler.kernel ||
+ Compiler.source =>
+ throw UnsupportedError(
+ 'The browser platform does not support $compiler'),
+ };
+ }
+
+ /// Compiles [dartPath] using [suiteConfig] for [platform].
+ @override
+ Future<void> compileSuite(String dartPath, SuiteConfiguration suiteConfig,
+ SuitePlatform platform) async {
+ if (suiteConfig.jsTrace) return;
+ var mapPath = p.join(
+ suiteConfig.precompiledPath!, '$dartPath.browser_test.dart.js.map');
+ var mapFile = File(mapPath);
+ if (mapFile.existsSync()) {
+ _mappers[dartPath] = JSStackTraceMapper(mapFile.readAsStringSync(),
+ mapUrl: p.toUri(mapPath),
+ sdkRoot: Uri.parse(r'/packages/$sdk'),
+ packageMap: (await currentPackageConfig).toPackageMap());
+ }
+ }
+
+ /// Retrieves a stack trace mapper for [path] if available.
+ @override
+ StackTraceMapper? stackTraceMapperForPath(String dartPath) =>
+ _mappers[dartPath];
+
+ /// Closes down anything necessary for this implementation.
+ @override
+ Future<void> close() async {
+ if (_closed) return;
+ _closed = true;
+ await _server.close();
+ }
+
+ @override
+ (Uri, Future<WebSocketChannel>) get webSocket {
+ var completer = Completer<WebSocketChannel>.sync();
+ // Note: the WebSocketChannel type below is needed for compatibility with
+ // package:shelf_web_socket v2.
+ var path =
+ _webSocketHandler.create(webSocketHandler((WebSocketChannel ws, _) {
+ completer.complete(ws);
+ }));
+ var webSocketUrl = serverUrl.replace(scheme: 'ws').resolve(path);
+ return (webSocketUrl, completer.future);
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/default_settings.dart b/pkgs/test/lib/src/runner/browser/default_settings.dart
new file mode 100644
index 0000000..312fc11
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/default_settings.dart
@@ -0,0 +1,37 @@
+// 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.
+
+import 'dart:collection';
+
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import '../executable_settings.dart';
+
+/// Default settings for starting browser executables.
+final defaultSettings = UnmodifiableMapView({
+ Runtime.chrome: ExecutableSettings(
+ linuxExecutable: 'google-chrome',
+ macOSExecutable:
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
+ windowsExecutable: r'Google\Chrome\Application\chrome.exe',
+ environmentOverride: 'CHROME_EXECUTABLE'),
+ Runtime.edge: ExecutableSettings(
+ linuxExecutable: 'microsoft-edge-stable',
+ windowsExecutable: r'Microsoft\Edge\Application\msedge.exe',
+ macOSExecutable:
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
+ environmentOverride: 'MS_EDGE_EXECUTABLE',
+ ),
+ Runtime.firefox: ExecutableSettings(
+ linuxExecutable: 'firefox',
+ macOSExecutables: [
+ '/Applications/Firefox.app/Contents/MacOS/firefox-bin',
+ '/Applications/Firefox.app/Contents/MacOS/firefox',
+ 'firefox',
+ ],
+ windowsExecutable: r'Mozilla Firefox\firefox.exe',
+ environmentOverride: 'FIREFOX_EXECUTABLE'),
+ Runtime.safari: ExecutableSettings(
+ macOSExecutable: '/Applications/Safari.app/Contents/MacOS/Safari',
+ environmentOverride: 'SAFARI_EXECUTABLE'),
+});
diff --git a/pkgs/test/lib/src/runner/browser/dom.dart b/pkgs/test/lib/src/runner/browser/dom.dart
new file mode 100644
index 0000000..d0fba48
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/dom.dart
@@ -0,0 +1,280 @@
+// Copyright (c) 2022, 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:js_util' as js_util;
+
+import 'package:js/js.dart';
+
+@JS()
+@staticInterop
+class Window extends EventTarget {}
+
+extension WindowExtension on Window {
+ @pragma('dart2js:as:trust')
+ Window get parent => js_util.getProperty<dynamic>(this, 'parent') as Window;
+ external Location get location;
+ Console get console => js_util.getProperty(this, 'console') as Console;
+ CSSStyleDeclaration? getComputedStyle(Element elt, [String? pseudoElt]) =>
+ js_util.callMethod(this, 'getComputedStyle', <Object>[
+ elt,
+ if (pseudoElt != null) pseudoElt
+ ]) as CSSStyleDeclaration?;
+ external Navigator get navigator;
+ void postMessage(Object message, String targetOrigin,
+ [List<MessagePort>? messagePorts]) =>
+ js_util.callMethod(this, 'postMessage', <Object?>[
+ js_util.jsify(message),
+ targetOrigin,
+ if (messagePorts != null) js_util.jsify(messagePorts)
+ ]);
+}
+
+@JS('window')
+external Window get window;
+
+@JS()
+@staticInterop
+class Console {}
+
+extension ConsoleExtension on Console {
+ external void log(Object? object);
+ external void warn(Object? object);
+}
+
+@JS()
+@staticInterop
+class Document extends Node {}
+
+extension DocumentExtension on Document {
+ external Element? querySelector(String selectors);
+ Element createElement(String name, [Object? options]) => js_util.callMethod(
+ this, 'createElement', <Object>[name, if (options != null) options])
+ as Element;
+}
+
+@JS()
+@staticInterop
+class HTMLDocument extends Document {}
+
+extension HTMLDocumentExtension on HTMLDocument {
+ external HTMLBodyElement? get body;
+ external String? get title;
+}
+
+@JS('document')
+external HTMLDocument get document;
+
+@JS()
+@staticInterop
+class Navigator {}
+
+extension NavigatorExtension on Navigator {
+ external String get userAgent;
+}
+
+@JS()
+@staticInterop
+class Element extends Node {}
+
+extension DomElementExtension on Element {
+ external DomTokenList get classList;
+}
+
+@JS()
+@staticInterop
+class HTMLElement extends Element {}
+
+@JS()
+@staticInterop
+class HTMLBodyElement extends HTMLElement {}
+
+@JS()
+@staticInterop
+class Node extends EventTarget {}
+
+extension NodeExtension on Node {
+ external Node appendChild(Node node);
+ void remove() {
+ if (parentNode != null) {
+ final parent = parentNode!;
+ parent.removeChild(this);
+ }
+ }
+
+ external Node removeChild(Node child);
+ external Node? get parentNode;
+}
+
+@JS()
+@staticInterop
+class EventTarget {}
+
+extension EventTargetExtension on EventTarget {
+ void addEventListener(String type, EventListener? listener,
+ [bool? useCapture]) {
+ if (listener != null) {
+ js_util.callMethod<void>(this, 'addEventListener',
+ <Object>[type, listener, if (useCapture != null) useCapture]);
+ }
+ }
+
+ void removeEventListener(String type, EventListener? listener,
+ [bool? useCapture]) {
+ if (listener != null) {
+ js_util.callMethod<void>(this, 'removeEventListener',
+ <Object>[type, listener, if (useCapture != null) useCapture]);
+ }
+ }
+}
+
+typedef EventListener = void Function(Event event);
+
+@JS()
+@staticInterop
+class Event {}
+
+extension EventExtension on Event {
+ external void stopPropagation();
+}
+
+@JS()
+@staticInterop
+class MessageEvent extends Event {}
+
+extension MessageEventExtension on MessageEvent {
+ dynamic get data => js_util.dartify(js_util.getProperty(this, 'data'));
+ external String get origin;
+ List<MessagePort> get ports =>
+ js_util.getProperty<List>(this, 'ports').cast<MessagePort>();
+
+ /// The source may be a `WindowProxy`, a `MessagePort`, or a `ServiceWorker`.
+ ///
+ /// When a message is sent from an iframe through `window.parent.postMessage`
+ /// the source will be a `WindowProxy` which has the same methods as [Window].
+ @pragma('dart2js:as:trust')
+ MessageEventSource get source =>
+ js_util.getProperty<dynamic>(this, 'source') as MessageEventSource;
+}
+
+@JS()
+@staticInterop
+class MessageEventSource {}
+
+extension MessageEventSourceExtension on MessageEventSource {
+ @pragma('dart2js:as:trust')
+ MessageEventSourceLocation? get location =>
+ js_util.getProperty<dynamic>(this, 'location')
+ as MessageEventSourceLocation;
+}
+
+@JS()
+@staticInterop
+class MessageEventSourceLocation {}
+
+extension MessageEventSourceLocationExtension on MessageEventSourceLocation {
+ external String? get href;
+}
+
+@JS()
+@staticInterop
+class Location {}
+
+extension LocationExtension on Location {
+ external String get href;
+ external String get origin;
+}
+
+@JS()
+@staticInterop
+class MessagePort extends EventTarget {}
+
+extension MessagePortExtension on MessagePort {
+ void postMessage(Object? message) => js_util.callMethod(this, 'postMessage',
+ <Object>[if (message != null) js_util.jsify(message) as Object]);
+ external void start();
+}
+
+@JS()
+@staticInterop
+class CSSStyleDeclaration {}
+
+@JS()
+@staticInterop
+class HTMLScriptElement extends HTMLElement {}
+
+extension HTMLScriptElementExtension on HTMLScriptElement {
+ external set src(String value);
+}
+
+HTMLScriptElement createHTMLScriptElement() =>
+ document.createElement('script') as HTMLScriptElement;
+
+@JS()
+@staticInterop
+class DomTokenList {}
+
+extension DomTokenListExtension on DomTokenList {
+ external void add(String value);
+ external void remove(String value);
+ external bool contains(String token);
+}
+
+@JS()
+@staticInterop
+class HTMLIFrameElement extends HTMLElement {}
+
+extension HTMLIFrameElementExtension on HTMLIFrameElement {
+ external String? get src;
+ external set src(String? value);
+ external Window get contentWindow;
+}
+
+HTMLIFrameElement createHTMLIFrameElement() =>
+ document.createElement('iframe') as HTMLIFrameElement;
+
+@JS()
+@staticInterop
+class WebSocket extends EventTarget {}
+
+extension WebSocketExtension on WebSocket {
+ external void send(Object? data);
+}
+
+WebSocket createWebSocket(String url) =>
+ _callConstructor('WebSocket', <Object>[url])! as WebSocket;
+
+@JS()
+@staticInterop
+class MessageChannel {}
+
+extension MessageChannelExtension on MessageChannel {
+ external MessagePort get port1;
+ external MessagePort get port2;
+}
+
+MessageChannel createMessageChannel() =>
+ _callConstructor('MessageChannel', <Object>[])! as MessageChannel;
+
+Object? _findConstructor(String constructorName) =>
+ js_util.getProperty(window, constructorName);
+
+Object? _callConstructor(String constructorName, List<Object?> args) {
+ final constructor = _findConstructor(constructorName);
+ if (constructor == null) {
+ return null;
+ }
+ return js_util.callConstructor(constructor, args);
+}
+
+class Subscription {
+ final String type;
+ final EventTarget target;
+ final EventListener listener;
+
+ Subscription(this.target, this.type, this.listener) {
+ target.addEventListener(type, listener);
+ }
+
+ void cancel() => target.removeEventListener(type, listener);
+}
diff --git a/pkgs/test/lib/src/runner/browser/firefox.dart b/pkgs/test/lib/src/runner/browser/firefox.dart
new file mode 100644
index 0000000..503f103
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/firefox.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2015, 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:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+
+import '../executable_settings.dart';
+import 'browser.dart';
+import 'default_settings.dart';
+
+final _preferences = '''
+user_pref("browser.shell.checkDefaultBrowser", false);
+user_pref("dom.disable_open_during_load", false);
+user_pref("dom.max_script_run_time", 0);
+''';
+
+/// A class for running an instance of Firefox.
+///
+/// Most of the communication with the browser is expected to happen via HTTP,
+/// so this exposes a bare-bones API. The browser starts as soon as the class is
+/// constructed, and is killed when [close] is called.
+///
+/// Any errors starting or running the process are reported through [onExit].
+class Firefox extends Browser {
+ @override
+ final name = 'Firefox';
+
+ Firefox(Uri url, {ExecutableSettings? settings})
+ : super(() =>
+ _startBrowser(url, settings ?? defaultSettings[Runtime.firefox]!));
+
+ /// Starts a new instance of Firefox open to the given [url], which may be a
+ /// [Uri] or a [String].
+ static Future<Process> _startBrowser(
+ Uri url, ExecutableSettings settings) async {
+ var dir = createTempDir();
+ File(p.join(dir, 'prefs.js')).writeAsStringSync(_preferences);
+
+ var process = await Process.start(settings.executable, [
+ '--profile',
+ dir,
+ url.toString(),
+ '--no-remote',
+ ...settings.arguments,
+ ], environment: {
+ 'MOZ_CRASHREPORTER_DISABLE': '1',
+ 'MOZ_AUTOMATION': '1',
+ });
+
+ unawaited(process.exitCode.then((_) => Directory(dir).deleteWithRetry()));
+
+ return process;
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/microsoft_edge.dart b/pkgs/test/lib/src/runner/browser/microsoft_edge.dart
new file mode 100644
index 0000000..08fa8e2
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/microsoft_edge.dart
@@ -0,0 +1,23 @@
+// 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 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
+
+import '../executable_settings.dart';
+import 'browser.dart';
+import 'chromium.dart';
+
+/// A class for running an instance of Microsoft Edge, a Chromium-based browser.
+class MicrosoftEdge extends Browser {
+ @override
+ String get name => 'Edge';
+
+ MicrosoftEdge(Uri url, Configuration configuration,
+ {ExecutableSettings? settings})
+ : super(() => ChromiumBasedBrowser.microsoftEdge.spawn(
+ url,
+ configuration,
+ settings: settings,
+ ));
+}
diff --git a/pkgs/test/lib/src/runner/browser/platform.dart b/pkgs/test/lib/src/runner/browser/platform.dart
new file mode 100644
index 0000000..da03a3e
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/platform.dart
@@ -0,0 +1,269 @@
+// Copyright (c) 2016, 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:io';
+
+import 'package:async/async.dart';
+import 'package:path/path.dart' as p;
+import 'package:test_api/backend.dart' show Compiler, Runtime, SuitePlatform;
+import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/load_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/platform.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/customizable_platform.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/package_config.dart'; // ignore: implementation_imports
+import 'package:yaml/yaml.dart';
+
+import '../executable_settings.dart';
+import 'browser_manager.dart';
+import 'compilers/compiler_support.dart';
+import 'compilers/dart2js.dart';
+import 'compilers/dart2wasm.dart';
+import 'compilers/precompiled.dart';
+import 'default_settings.dart';
+
+class BrowserPlatform extends PlatformPlugin
+ implements CustomizablePlatform<ExecutableSettings> {
+ /// Starts the server.
+ ///
+ /// [root] is the root directory that the server should serve. It defaults to
+ /// the working directory.
+ static Future<BrowserPlatform> start({String? root}) async {
+ var packageConfig = await currentPackageConfig;
+ return BrowserPlatform._(
+ Configuration.current,
+ p.fromUri(packageConfig.resolve(
+ Uri.parse('package:test/src/runner/browser/static/favicon.ico'))),
+ p.fromUri(packageConfig.resolve(Uri.parse(
+ 'package:test/src/runner/browser/static/default.html.tpl'))),
+ p.fromUri(packageConfig.resolve(Uri.parse(
+ 'package:test/src/runner/browser/static/run_wasm_chrome.js'))),
+ root: root);
+ }
+
+ /// The test runner configuration.
+ final Configuration _config;
+
+ /// The cached [CompilerSupport] for each compiler.
+ final _compilerSupport = <Compiler, Future<CompilerSupport>>{};
+
+ /// The `package:test` side wrapper for the Dart2Wasm runtime.
+ final String _jsRuntimeWrapper;
+
+ /// The URL for this server and [compiler] combination.
+ ///
+ /// Each compiler serves its tests under a different randomly-generated
+ /// secret URI to ensure that other users on the same system can't snoop
+ /// on data being served through this server, as well as distinguish tests
+ /// from different compilers from each other.
+ Future<CompilerSupport> compilerSupport(Compiler compiler) =>
+ _compilerSupport.putIfAbsent(compiler, () {
+ if (_config.suiteDefaults.precompiledPath != null) {
+ return PrecompiledSupport.start(
+ compiler: compiler,
+ config: _config,
+ defaultTemplatePath: _defaultTemplatePath,
+ root: _config.suiteDefaults.precompiledPath!,
+ faviconPath: _faviconPath);
+ }
+ return switch (compiler) {
+ Compiler.dart2js => Dart2JsSupport.start(
+ config: _config,
+ defaultTemplatePath: _defaultTemplatePath,
+ root: _root,
+ faviconPath: _faviconPath),
+ Compiler.dart2wasm => Dart2WasmSupport.start(
+ config: _config,
+ defaultTemplatePath: _defaultTemplatePath,
+ jsRuntimeWrapper: _jsRuntimeWrapper,
+ root: _root,
+ faviconPath: _faviconPath),
+ _ => throw StateError('Unexpected compiler $compiler'),
+ };
+ });
+
+ /// The root directory served statically by this server.
+ final String _root;
+
+ /// Whether [close] has been called.
+ bool get _closed => _closeMemo.hasRun;
+
+ /// A map from browser identifiers to futures that will complete to the
+ /// [BrowserManager]s for those browsers, or `null` if they failed to load.
+ ///
+ /// This should only be accessed through [_browserManagerFor].
+ final _browserManagers = <(Runtime, Compiler), Future<BrowserManager?>>{};
+
+ /// Settings for invoking each browser.
+ ///
+ /// This starts out with the default settings, which may be overridden by user settings.
+ final _browserSettings =
+ Map<Runtime, ExecutableSettings>.from(defaultSettings);
+
+ /// The default template for html tests.
+ final String _defaultTemplatePath;
+
+ final String _faviconPath;
+
+ BrowserPlatform._(Configuration config, this._faviconPath,
+ this._defaultTemplatePath, this._jsRuntimeWrapper,
+ {String? root})
+ : _config = config,
+ _root = root ?? p.current;
+
+ @override
+ ExecutableSettings parsePlatformSettings(YamlMap settings) =>
+ ExecutableSettings.parse(settings);
+
+ @override
+ ExecutableSettings mergePlatformSettings(
+ ExecutableSettings settings1, ExecutableSettings settings2) =>
+ settings1.merge(settings2);
+
+ @override
+ void customizePlatform(Runtime runtime, ExecutableSettings settings) {
+ var oldSettings =
+ _browserSettings[runtime] ?? _browserSettings[runtime.root];
+ if (oldSettings != null) settings = oldSettings.merge(settings);
+ _browserSettings[runtime] = settings;
+ }
+
+ /// Loads the test suite at [path] on the platform [platform].
+ ///
+ /// This will start a browser to load the suite if one isn't already running.
+ /// Throws an [ArgumentError] if `platform.platform` isn't a browser.
+ @override
+ Future<RunnerSuite?> load(String path, SuitePlatform platform,
+ SuiteConfiguration suiteConfig, Map<String, Object?> message) async {
+ var browser = platform.runtime;
+ assert(suiteConfig.runtimes.contains(browser.identifier));
+
+ if (!browser.isBrowser) {
+ throw ArgumentError('$browser is not a browser.');
+ }
+
+ var compiler = platform.compiler;
+ var support = await compilerSupport(compiler);
+
+ var htmlPathFromTestPath = '${p.withoutExtension(path)}.html';
+ if (File(htmlPathFromTestPath).existsSync()) {
+ if (_config.customHtmlTemplatePath != null &&
+ p.basename(htmlPathFromTestPath) ==
+ p.basename(_config.customHtmlTemplatePath!)) {
+ throw LoadException(
+ path,
+ 'template file "${p.basename(_config.customHtmlTemplatePath!)}" cannot be named '
+ 'like the test file.');
+ }
+ _checkHtmlCorrectness(htmlPathFromTestPath, path);
+ } else if (_config.customHtmlTemplatePath != null) {
+ var htmlTemplatePath = _config.customHtmlTemplatePath!;
+ if (!File(htmlTemplatePath).existsSync()) {
+ throw LoadException(
+ path, '"$htmlTemplatePath" does not exist or is not readable');
+ }
+
+ final templateFileContents = File(htmlTemplatePath).readAsStringSync();
+ if ('{{testScript}}'.allMatches(templateFileContents).length != 1) {
+ throw LoadException(path,
+ '"$htmlTemplatePath" must contain exactly one {{testScript}} placeholder');
+ }
+ _checkHtmlCorrectness(htmlTemplatePath, path);
+ }
+
+ if (_closed) return null;
+ await support.compileSuite(path, suiteConfig, platform);
+
+ var suiteUrl = support.serverUrl.resolveUri(
+ p.toUri('${p.withoutExtension(p.relative(path, from: _root))}.html'));
+
+ if (_closed) return null;
+
+ var browserManager = await _browserManagerFor(browser, compiler);
+ if (_closed || browserManager == null) return null;
+
+ var timeout = const Duration(seconds: 30);
+ if (suiteConfig.metadata.timeout.apply(timeout) case final suiteTimeout?
+ when suiteTimeout > timeout) {
+ timeout = suiteTimeout;
+ }
+ var suite = await browserManager.load(
+ path, suiteUrl, suiteConfig, message, platform.compiler,
+ mapper: (await compilerSupport(compiler)).stackTraceMapperForPath(path),
+ timeout: timeout);
+ if (_closed) return null;
+ return suite;
+ }
+
+ void _checkHtmlCorrectness(String htmlPath, String path) {
+ if (!File(htmlPath).readAsStringSync().contains('packages/test/dart.js')) {
+ throw LoadException(
+ path,
+ '"$htmlPath" must contain <script src="packages/test/dart.js">'
+ '</script>.');
+ }
+ }
+
+ /// Returns the [BrowserManager] for [runtime], which should be a browser.
+ ///
+ /// If no browser manager is running yet, starts one.
+ Future<BrowserManager?> _browserManagerFor(
+ Runtime browser, Compiler compiler) {
+ var managerFuture = _browserManagers[(browser, compiler)];
+ if (managerFuture != null) return managerFuture;
+
+ var future = _createBrowserManager(browser, compiler);
+ // Store null values for browsers that error out so we know not to load them
+ // again.
+ _browserManagers[(browser, compiler)] =
+ future.then<BrowserManager?>((value) => value).onError((_, __) => null);
+
+ return future;
+ }
+
+ Future<BrowserManager> _createBrowserManager(
+ Runtime browser, Compiler compiler) async {
+ var support = await compilerSupport(compiler);
+ var (webSocketUrl, socketFuture) = support.webSocket;
+ var hostUrl = support.serverUrl
+ .resolve('packages/test/src/runner/browser/static/index.html')
+ .replace(queryParameters: {
+ 'managerUrl': webSocketUrl.toString(),
+ 'debug': _config.debug.toString()
+ });
+
+ return BrowserManager.start(
+ browser, hostUrl, socketFuture, _browserSettings[browser]!, _config);
+ }
+
+ /// Close all the browsers that the server currently has open.
+ ///
+ /// Note that this doesn't close the server itself. Browser tests can still be
+ /// loaded, they'll just spawn new browsers.
+ @override
+ Future<List<void>> closeEphemeral() {
+ var managers = _browserManagers.values.toList();
+ _browserManagers.clear();
+ return Future.wait(managers.map((manager) async {
+ var result = await manager;
+ if (result == null) return;
+ await result.close();
+ }));
+ }
+
+ /// Closes the server and releases all its resources.
+ ///
+ /// Returns a [Future] that completes once the server is closed and its
+ /// resources have been fully released.
+ @override
+ Future<void> close() async => _closeMemo.runOnce(() => Future.wait([
+ for (var browser in _browserManagers.values)
+ browser.then((b) => b?.close()),
+ for (var support in _compilerSupport.values)
+ support.then((s) => s.close()),
+ ]));
+ final _closeMemo = AsyncMemoizer<void>();
+}
diff --git a/pkgs/test/lib/src/runner/browser/post_message_channel.dart b/pkgs/test/lib/src/runner/browser/post_message_channel.dart
new file mode 100644
index 0000000..60a5a6b
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/post_message_channel.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2015, 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:js_util';
+
+import 'package:stream_channel/stream_channel.dart';
+
+import 'dom.dart' as dom;
+
+/// Constructs a [StreamChannel] wrapping a new [MessageChannel] communicating
+/// with the host page.
+///
+/// Sends a [MessagePort] to the host page for the channel.
+StreamChannel<Object?> postMessageChannel() {
+ dom.window.console.log('Suite starting, sending channel to host');
+ var controller = StreamChannelController<Object?>(sync: true);
+ var channel = dom.createMessageChannel();
+ dom.window.parent
+ .postMessage('port', dom.window.location.origin, [channel.port2]);
+ var portSubscription = dom.Subscription(channel.port1, 'message',
+ allowInterop((dom.Event event) {
+ controller.local.sink.add((event as dom.MessageEvent).data);
+ }));
+ channel.port1.start();
+
+ controller.local.stream
+ .listen(channel.port1.postMessage, onDone: portSubscription.cancel);
+
+ return controller.foreign;
+}
diff --git a/pkgs/test/lib/src/runner/browser/safari.dart b/pkgs/test/lib/src/runner/browser/safari.dart
new file mode 100644
index 0000000..4e97205
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/safari.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2015, 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:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+
+import '../executable_settings.dart';
+import 'browser.dart';
+import 'default_settings.dart';
+
+/// A class for running an instance of Safari.
+///
+/// Any errors starting or running the process are reported through [onExit].
+class Safari extends Browser {
+ @override
+ final name = 'Safari';
+
+ Safari(Uri url, {ExecutableSettings? settings})
+ : super(() =>
+ _startBrowser(url, settings ?? defaultSettings[Runtime.safari]!));
+
+ /// Starts a new instance of Safari open to the given [url], which may be a
+ /// [Uri] or a [String].
+ static Future<Process> _startBrowser(
+ Uri url, ExecutableSettings settings) async {
+ var dir = createTempDir();
+
+ // Safari will only open files (not general URLs) via the command-line
+ // API, so we create a dummy file to redirect it to the page we actually
+ // want it to load.
+ var redirect = p.join(dir, 'redirect.html');
+ File(redirect).writeAsStringSync(
+ '<script>location = ${jsonEncode(url.toString())}</script>');
+
+ var process = await Process.start(
+ settings.executable, settings.arguments.toList()..add(redirect));
+
+ unawaited(process.exitCode.then((_) => Directory(dir).deleteWithRetry()));
+
+ return process;
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/static/default.html.tpl b/pkgs/test/lib/src/runner/browser/static/default.html.tpl
new file mode 100644
index 0000000..a92f529
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/static/default.html.tpl
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>{{testName}} Test</title>
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+ </head>
+</html>
diff --git a/pkgs/test/lib/src/runner/browser/static/favicon.ico b/pkgs/test/lib/src/runner/browser/static/favicon.ico
new file mode 100644
index 0000000..7ba349b
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/static/favicon.ico
Binary files differ
diff --git a/pkgs/test/lib/src/runner/browser/static/host.css b/pkgs/test/lib/src/runner/browser/static/host.css
new file mode 100644
index 0000000..b8e0352
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/static/host.css
@@ -0,0 +1,316 @@
+/* Copyright (c) 2015, 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. */
+
+iframe {
+ /* We would use display: none here, but then Firefox fails to properly compute
+ * styles. See #274 */
+ visibility: hidden;
+ width: 1024px;
+ height: 768px;
+}
+
+#play {
+ display: none;
+ cursor: pointer;
+}
+
+#dark {
+ display: none;
+}
+
+.paused #play {
+ display: block;
+ z-index: 1;
+}
+
+.paused #dark {
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ right: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+}
+
+.paused #right-flank, .paused #right-ear, .paused #right-paw,
+.paused #left-flank, .paused #left-ear, .paused #left-paw {
+ -webkit-animation-play-state: paused;
+ animation-play-state: paused;
+}
+
+.debug body {
+ margin: 0;
+ padding: 0;
+}
+
+.debug iframe {
+ visibility: visible;
+ border: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+/* Compiled output from
+ * http://codepen.io/mknadler/pen/11b75cb014a3c382f54abf527655af21. */
+
+svg {
+ position: absolute;
+ margin: auto;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ top: 0;
+}
+
+#right-flank {
+ fill: #0074C1;
+ stroke-color: #0074C1;
+ -webkit-animation: right-flank 8s ease infinite alternate;
+ animation: right-flank 8s ease infinite alternate;
+}
+
+#right-ear {
+ fill: #00B5AB;
+ stroke-color: #00B5AB;
+ -webkit-animation: right-ear 8s ease-in infinite alternate;
+ animation: right-ear 8s ease-in infinite alternate;
+}
+
+#right-paw {
+ fill: #00A6E4;
+ stroke-color: #00A6E4;
+ -webkit-animation: right-paw 8s ease-out infinite alternate;
+ animation: right-paw 8s ease-out infinite alternate;
+}
+
+#left-flank {
+ fill: #00B5AB;
+ stroke-color: #00B5AB;
+ -webkit-animation: left-flank 8s ease-in-out infinite alternate;
+ animation: left-flank 8s ease-in-out infinite alternate;
+}
+
+#left-ear {
+ fill: #0074C1;
+ stroke-color: #0074C1;
+ -webkit-animation: left-ear 8s linear infinite alternate;
+ animation: left-ear 8s linear infinite alternate;
+}
+
+#left-paw {
+ fill: #41C1BC;
+ stroke-color: #41C1BC;
+ -webkit-animation: left-paw 8s ease infinite alternate;
+ animation: left-paw 8s ease infinite alternate;
+}
+
+@-webkit-keyframes left-ear {
+ 20% {
+ -webkit-transform: translate(250px, 150px) rotateY(180deg) scale(0.6);
+ transform: translate(250px, 150px) rotateY(180deg) scale(0.6);
+ fill: #00A6E4;
+ }
+ 50% {
+ -webkit-transform: translate(100px, 75px) rotateY(80deg) scale(1.1);
+ transform: translate(100px, 75px) rotateY(80deg) scale(1.1);
+ fill: #41C1BC;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #0074C1;
+ }
+}
+
+@keyframes left-ear {
+ 20% {
+ -webkit-transform: translate(250px, 150px) rotateY(180deg) scale(0.6);
+ transform: translate(250px, 150px) rotateY(180deg) scale(0.6);
+ fill: #00A6E4;
+ }
+ 50% {
+ -webkit-transform: translate(100px, 75px) rotateY(80deg) scale(1.1);
+ transform: translate(100px, 75px) rotateY(80deg) scale(1.1);
+ fill: #41C1BC;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #0074C1;
+ }
+}
+@-webkit-keyframes right-ear {
+ 20% {
+ -webkit-transform: translate(200px, 250px) rotateX(180deg) scale(0.6);
+ transform: translate(200px, 250px) rotateX(180deg) scale(0.6);
+ fill: #41C1BC;
+ }
+ 50% {
+ -webkit-transform: translate(75px, 100px) rotateX(80deg) scale(1.1);
+ transform: translate(75px, 100px) rotateX(80deg) scale(1.1);
+ fill: #00A6E4;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #00B5AB;
+ }
+}
+@keyframes right-ear {
+ 20% {
+ -webkit-transform: translate(200px, 250px) rotateX(180deg) scale(0.6);
+ transform: translate(200px, 250px) rotateX(180deg) scale(0.6);
+ fill: #41C1BC;
+ }
+ 50% {
+ -webkit-transform: translate(75px, 100px) rotateX(80deg) scale(1.1);
+ transform: translate(75px, 100px) rotateX(80deg) scale(1.1);
+ fill: #00A6E4;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #00B5AB;
+ }
+}
+@-webkit-keyframes left-paw {
+ 20% {
+ -webkit-transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, 60deg) scale(0.6);
+ transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, 60deg) scale(0.6);
+ fill: #00B5AB;
+ }
+ 50% {
+ -webkit-transform: translate(150px, 250px) rotate3d(-1, 0, 0.5, 90deg) scale(0.6);
+ transform: translate(150px, 250px) rotate3d(-1, 0, 0.5, 90deg) scale(0.6);
+ fill: #00B5AB;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #41C1BC;
+ }
+}
+@keyframes left-paw {
+ 20% {
+ -webkit-transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, 60deg) scale(0.6);
+ transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, 60deg) scale(0.6);
+ fill: #00B5AB;
+ }
+ 50% {
+ -webkit-transform: translate(150px, 250px) rotate3d(-1, 0, 0.5, 90deg) scale(0.6);
+ transform: translate(150px, 250px) rotate3d(-1, 0, 0.5, 90deg) scale(0.6);
+ fill: #00B5AB;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #41C1BC;
+ }
+}
+@-webkit-keyframes right-paw {
+ 20% {
+ -webkit-transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, -60deg) scale(0.6);
+ transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, -60deg) scale(0.6);
+ fill: #41C1BC;
+ }
+ 50% {
+ -webkit-transform: translate(100px, 250px) rotate3d(-1, 0, 0.5, -90deg) scale(0.6);
+ transform: translate(100px, 250px) rotate3d(-1, 0, 0.5, -90deg) scale(0.6);
+ fill: #41C1BC;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #00A6E4;
+ }
+}
+@keyframes right-paw {
+ 20% {
+ -webkit-transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, -60deg) scale(0.6);
+ transform: translate(200px, 200px) rotate3d(-1, 0, 0.5, -60deg) scale(0.6);
+ fill: #41C1BC;
+ }
+ 50% {
+ -webkit-transform: translate(100px, 250px) rotate3d(-1, 0, 0.5, -90deg) scale(0.6);
+ transform: translate(100px, 250px) rotate3d(-1, 0, 0.5, -90deg) scale(0.6);
+ fill: #41C1BC;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #00A6E4;
+ }
+}
+@-webkit-keyframes left-flank {
+ 20% {
+ -webkit-transform: translate(0px, 100px) scale(0.6);
+ transform: translate(0px, 100px) scale(0.6);
+ fill: #00A6E4;
+ }
+ 50% {
+ -webkit-transform: translate(0px, 100px) scale(0.4);
+ transform: translate(0px, 100px) scale(0.4);
+ fill: #00A6E4;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #00B5AB;
+ }
+}
+@keyframes left-flank {
+ 20% {
+ -webkit-transform: translate(0px, 100px) scale(0.6);
+ transform: translate(0px, 100px) scale(0.6);
+ fill: #00A6E4;
+ }
+ 50% {
+ -webkit-transform: translate(0px, 100px) scale(0.4);
+ transform: translate(0px, 100px) scale(0.4);
+ fill: #00A6E4;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #00B5AB;
+ }
+}
+@-webkit-keyframes right-flank {
+ 20% {
+ -webkit-transform: translate(100px, -25px) scale(0.6);
+ transform: translate(100px, -25px) scale(0.6);
+ fill: #41C1BC;
+ }
+ 50% {
+ -webkit-transform: translate(110px, 0px) scale(0.4);
+ transform: translate(110px, 0px) scale(0.4);
+ fill: #00A6E4;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #0074C1;
+ }
+}
+@keyframes right-flank {
+ 20% {
+ -webkit-transform: translate(100px, -25px) scale(0.6);
+ transform: translate(100px, -25px) scale(0.6);
+ fill: #41C1BC;
+ }
+ 50% {
+ -webkit-transform: translate(110px, 0px) scale(0.4);
+ transform: translate(110px, 0px) scale(0.4);
+ fill: #00A6E4;
+ }
+ 80% {
+ -webkit-transform: translate(0px, 0px) scale(1);
+ transform: translate(0px, 0px) scale(1);
+ fill: #0074C1;
+ }
+}
diff --git a/pkgs/test/lib/src/runner/browser/static/host.dart.js b/pkgs/test/lib/src/runner/browser/static/host.dart.js
new file mode 100644
index 0000000..fd3ef38
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/static/host.dart.js
@@ -0,0 +1,18333 @@
+// Generated by dart2js (NullSafetyMode.sound, csp, intern-composite-values), the Dart to JavaScript compiler version: 3.2.0-228.0.dev.
+// The code supports the following hooks:
+// dartPrint(message):
+// if this function is defined it is called instead of the Dart [print]
+// method.
+//
+// dartMainRunner(main, args):
+// if this function is defined, the Dart [main] method will not be invoked
+// directly. Instead, a closure that will invoke [main], and its arguments
+// [args] is passed to [dartMainRunner].
+//
+// dartDeferredLibraryLoader(uri, successCallback, errorCallback, loadId, loadPriority):
+// if this function is defined, it will be called when a deferred library
+// is loaded. It should load and eval the javascript of `uri`, and call
+// successCallback. If it fails to do so, it should call errorCallback with
+// an error. The loadId argument is the deferred import that resulted in
+// this uri being loaded. The loadPriority argument is the priority the
+// library should be loaded with as specified in the code via the
+// load-priority annotation (0: normal, 1: high).
+//
+// dartCallInstrumentation(id, qualifiedName):
+// if this function is defined, it will be called at each entry of a
+// method or constructor. Used only when compiling programs with
+// --experiment-call-instrumentation.
+(function dartProgram() {
+ function copyProperties(from, to) {
+ var keys = Object.keys(from);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ to[key] = from[key];
+ }
+ }
+ function mixinPropertiesHard(from, to) {
+ var keys = Object.keys(from);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ if (!to.hasOwnProperty(key))
+ to[key] = from[key];
+ }
+ }
+ function mixinPropertiesEasy(from, to) {
+ Object.assign(to, from);
+ }
+ var supportsDirectProtoAccess = function() {
+ var cls = function() {
+ };
+ cls.prototype = {p: {}};
+ var object = new cls();
+ if (!(Object.getPrototypeOf(object) && Object.getPrototypeOf(object).p === cls.prototype.p))
+ return false;
+ try {
+ if (typeof navigator != "undefined" && typeof navigator.userAgent == "string" && navigator.userAgent.indexOf("Chrome/") >= 0)
+ return true;
+ if (typeof version == "function" && version.length == 0) {
+ var v = version();
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(v))
+ return true;
+ }
+ } catch (_) {
+ }
+ return false;
+ }();
+ function inherit(cls, sup) {
+ cls.prototype.constructor = cls;
+ cls.prototype["$is" + cls.name] = cls;
+ if (sup != null) {
+ if (supportsDirectProtoAccess) {
+ Object.setPrototypeOf(cls.prototype, sup.prototype);
+ return;
+ }
+ var clsPrototype = Object.create(sup.prototype);
+ copyProperties(cls.prototype, clsPrototype);
+ cls.prototype = clsPrototype;
+ }
+ }
+ function inheritMany(sup, classes) {
+ for (var i = 0; i < classes.length; i++)
+ inherit(classes[i], sup);
+ }
+ function mixinEasy(cls, mixin) {
+ mixinPropertiesEasy(mixin.prototype, cls.prototype);
+ cls.prototype.constructor = cls;
+ }
+ function mixinHard(cls, mixin) {
+ mixinPropertiesHard(mixin.prototype, cls.prototype);
+ cls.prototype.constructor = cls;
+ }
+ function lazyOld(holder, name, getterName, initializer) {
+ var uninitializedSentinel = holder;
+ holder[name] = uninitializedSentinel;
+ holder[getterName] = function() {
+ holder[getterName] = function() {
+ A.throwCyclicInit(name);
+ };
+ var result;
+ var sentinelInProgress = initializer;
+ try {
+ if (holder[name] === uninitializedSentinel) {
+ result = holder[name] = sentinelInProgress;
+ result = holder[name] = initializer();
+ } else
+ result = holder[name];
+ } finally {
+ if (result === sentinelInProgress)
+ holder[name] = null;
+ holder[getterName] = function() {
+ return this[name];
+ };
+ }
+ return result;
+ };
+ }
+ function lazy(holder, name, getterName, initializer) {
+ var uninitializedSentinel = holder;
+ holder[name] = uninitializedSentinel;
+ holder[getterName] = function() {
+ if (holder[name] === uninitializedSentinel)
+ holder[name] = initializer();
+ holder[getterName] = function() {
+ return this[name];
+ };
+ return holder[name];
+ };
+ }
+ function lazyFinal(holder, name, getterName, initializer) {
+ var uninitializedSentinel = holder;
+ holder[name] = uninitializedSentinel;
+ holder[getterName] = function() {
+ if (holder[name] === uninitializedSentinel) {
+ var value = initializer();
+ if (holder[name] !== uninitializedSentinel)
+ A.throwLateFieldADI(name);
+ holder[name] = value;
+ }
+ var finalValue = holder[name];
+ holder[getterName] = function() {
+ return finalValue;
+ };
+ return finalValue;
+ };
+ }
+ function makeConstList(list) {
+ list.immutable$list = Array;
+ list.fixed$length = Array;
+ return list;
+ }
+ function convertToFastObject(properties) {
+ function t() {
+ }
+ t.prototype = properties;
+ new t();
+ return properties;
+ }
+ function convertAllToFastObject(arrayOfObjects) {
+ for (var i = 0; i < arrayOfObjects.length; ++i)
+ convertToFastObject(arrayOfObjects[i]);
+ }
+ var functionCounter = 0;
+ function instanceTearOffGetter(isIntercepted, parameters) {
+ var cache = null;
+ return isIntercepted ? function(receiver) {
+ if (cache === null)
+ cache = A.closureFromTearOff(parameters);
+ return new cache(receiver, this);
+ } : function() {
+ if (cache === null)
+ cache = A.closureFromTearOff(parameters);
+ return new cache(this, null);
+ };
+ }
+ function staticTearOffGetter(parameters) {
+ var cache = null;
+ return function() {
+ if (cache === null)
+ cache = A.closureFromTearOff(parameters).prototype;
+ return cache;
+ };
+ }
+ var typesOffset = 0;
+ function tearOffParameters(container, isStatic, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, needsDirectAccess) {
+ if (typeof funType == "number")
+ funType += typesOffset;
+ return {co: container, iS: isStatic, iI: isIntercepted, rC: requiredParameterCount, dV: optionalParameterDefaultValues, cs: callNames, fs: funsOrNames, fT: funType, aI: applyIndex || 0, nDA: needsDirectAccess};
+ }
+ function installStaticTearOff(holder, getterName, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex) {
+ var parameters = tearOffParameters(holder, true, false, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, false);
+ var getterFunction = staticTearOffGetter(parameters);
+ holder[getterName] = getterFunction;
+ }
+ function installInstanceTearOff(prototype, getterName, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, needsDirectAccess) {
+ isIntercepted = !!isIntercepted;
+ var parameters = tearOffParameters(prototype, false, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex, !!needsDirectAccess);
+ var getterFunction = instanceTearOffGetter(isIntercepted, parameters);
+ prototype[getterName] = getterFunction;
+ }
+ function setOrUpdateInterceptorsByTag(newTags) {
+ var tags = init.interceptorsByTag;
+ if (!tags) {
+ init.interceptorsByTag = newTags;
+ return;
+ }
+ copyProperties(newTags, tags);
+ }
+ function setOrUpdateLeafTags(newTags) {
+ var tags = init.leafTags;
+ if (!tags) {
+ init.leafTags = newTags;
+ return;
+ }
+ copyProperties(newTags, tags);
+ }
+ function updateTypes(newTypes) {
+ var types = init.types;
+ var length = types.length;
+ types.push.apply(types, newTypes);
+ return length;
+ }
+ function updateHolder(holder, newHolder) {
+ copyProperties(newHolder, holder);
+ return holder;
+ }
+ var hunkHelpers = function() {
+ var mkInstance = function(isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, applyIndex) {
+ return function(container, getterName, name, funType) {
+ return installInstanceTearOff(container, getterName, isIntercepted, requiredParameterCount, optionalParameterDefaultValues, callNames, [name], funType, applyIndex, false);
+ };
+ },
+ mkStatic = function(requiredParameterCount, optionalParameterDefaultValues, callNames, applyIndex) {
+ return function(container, getterName, name, funType) {
+ return installStaticTearOff(container, getterName, requiredParameterCount, optionalParameterDefaultValues, callNames, [name], funType, applyIndex);
+ };
+ };
+ return {inherit: inherit, inheritMany: inheritMany, mixin: mixinEasy, mixinHard: mixinHard, installStaticTearOff: installStaticTearOff, installInstanceTearOff: installInstanceTearOff, _instance_0u: mkInstance(0, 0, null, ["call$0"], 0), _instance_1u: mkInstance(0, 1, null, ["call$1"], 0), _instance_2u: mkInstance(0, 2, null, ["call$2"], 0), _instance_0i: mkInstance(1, 0, null, ["call$0"], 0), _instance_1i: mkInstance(1, 1, null, ["call$1"], 0), _instance_2i: mkInstance(1, 2, null, ["call$2"], 0), _static_0: mkStatic(0, null, ["call$0"], 0), _static_1: mkStatic(1, null, ["call$1"], 0), _static_2: mkStatic(2, null, ["call$2"], 0), makeConstList: makeConstList, lazy: lazy, lazyFinal: lazyFinal, lazyOld: lazyOld, updateHolder: updateHolder, convertToFastObject: convertToFastObject, updateTypes: updateTypes, setOrUpdateInterceptorsByTag: setOrUpdateInterceptorsByTag, setOrUpdateLeafTags: setOrUpdateLeafTags};
+ }();
+ function initializeDeferredHunk(hunk) {
+ typesOffset = init.types.length;
+ hunk(hunkHelpers, init, holders, $);
+ }
+ var J = {
+ makeDispatchRecord(interceptor, proto, extension, indexability) {
+ return {i: interceptor, p: proto, e: extension, x: indexability};
+ },
+ getNativeInterceptor(object) {
+ var proto, objectProto, $constructor, interceptor, t1,
+ record = object[init.dispatchPropertyName];
+ if (record == null)
+ if ($.initNativeDispatchFlag == null) {
+ A.initNativeDispatch();
+ record = object[init.dispatchPropertyName];
+ }
+ if (record != null) {
+ proto = record.p;
+ if (false === proto)
+ return record.i;
+ if (true === proto)
+ return object;
+ objectProto = Object.getPrototypeOf(object);
+ if (proto === objectProto)
+ return record.i;
+ if (record.e === objectProto)
+ throw A.wrapException(A.UnimplementedError$("Return interceptor for " + A.S(proto(object, record))));
+ }
+ $constructor = object.constructor;
+ if ($constructor == null)
+ interceptor = null;
+ else {
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG;
+ if (t1 == null)
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG = init.getIsolateTag("_$dart_js");
+ interceptor = $constructor[t1];
+ }
+ if (interceptor != null)
+ return interceptor;
+ interceptor = A.lookupAndCacheInterceptor(object);
+ if (interceptor != null)
+ return interceptor;
+ if (typeof object == "function")
+ return B.JavaScriptFunction_methods;
+ proto = Object.getPrototypeOf(object);
+ if (proto == null)
+ return B.PlainJavaScriptObject_methods;
+ if (proto === Object.prototype)
+ return B.PlainJavaScriptObject_methods;
+ if (typeof $constructor == "function") {
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG;
+ if (t1 == null)
+ t1 = $._JS_INTEROP_INTERCEPTOR_TAG = init.getIsolateTag("_$dart_js");
+ Object.defineProperty($constructor, t1, {value: B.UnknownJavaScriptObject_methods, enumerable: false, writable: true, configurable: true});
+ return B.UnknownJavaScriptObject_methods;
+ }
+ return B.UnknownJavaScriptObject_methods;
+ },
+ JSArray_JSArray$fixed($length, $E) {
+ if ($length < 0 || $length > 4294967295)
+ throw A.wrapException(A.RangeError$range($length, 0, 4294967295, "length", null));
+ return J.JSArray_JSArray$markFixed(new Array($length), $E);
+ },
+ JSArray_JSArray$growable($length, $E) {
+ if ($length < 0)
+ throw A.wrapException(A.ArgumentError$("Length must be a non-negative integer: " + $length, null));
+ return A._setArrayType(new Array($length), $E._eval$1("JSArray<0>"));
+ },
+ JSArray_JSArray$markFixed(allocation, $E) {
+ return J.JSArray_markFixedList(A._setArrayType(allocation, $E._eval$1("JSArray<0>")), $E);
+ },
+ JSArray_markFixedList(list, $T) {
+ list.fixed$length = Array;
+ return list;
+ },
+ JSArray_markUnmodifiableList(list) {
+ list.fixed$length = Array;
+ list.immutable$list = Array;
+ return list;
+ },
+ JSString__isWhitespace(codeUnit) {
+ if (codeUnit < 256)
+ switch (codeUnit) {
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 32:
+ case 133:
+ case 160:
+ return true;
+ default:
+ return false;
+ }
+ switch (codeUnit) {
+ case 5760:
+ case 8192:
+ case 8193:
+ case 8194:
+ case 8195:
+ case 8196:
+ case 8197:
+ case 8198:
+ case 8199:
+ case 8200:
+ case 8201:
+ case 8202:
+ case 8232:
+ case 8233:
+ case 8239:
+ case 8287:
+ case 12288:
+ case 65279:
+ return true;
+ default:
+ return false;
+ }
+ },
+ JSString__skipLeadingWhitespace(string, index) {
+ var t1, codeUnit;
+ for (t1 = string.length; index < t1;) {
+ codeUnit = string.charCodeAt(index);
+ if (codeUnit !== 32 && codeUnit !== 13 && !J.JSString__isWhitespace(codeUnit))
+ break;
+ ++index;
+ }
+ return index;
+ },
+ JSString__skipTrailingWhitespace(string, index) {
+ var t1, index0, codeUnit;
+ for (t1 = string.length; index > 0; index = index0) {
+ index0 = index - 1;
+ if (!(index0 < t1))
+ return A.ioore(string, index0);
+ codeUnit = string.charCodeAt(index0);
+ if (codeUnit !== 32 && codeUnit !== 13 && !J.JSString__isWhitespace(codeUnit))
+ break;
+ }
+ return index;
+ },
+ getInterceptor$(receiver) {
+ if (typeof receiver == "number") {
+ if (Math.floor(receiver) == receiver)
+ return J.JSInt.prototype;
+ return J.JSNumNotInt.prototype;
+ }
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return J.JSNull.prototype;
+ if (typeof receiver == "boolean")
+ return J.JSBool.prototype;
+ if (Array.isArray(receiver))
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ if (typeof receiver == "symbol")
+ return J.JavaScriptSymbol.prototype;
+ if (typeof receiver == "bigint")
+ return J.JavaScriptBigInt.prototype;
+ return receiver;
+ }
+ if (receiver instanceof A.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$asx(receiver) {
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return receiver;
+ if (Array.isArray(receiver))
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ if (typeof receiver == "symbol")
+ return J.JavaScriptSymbol.prototype;
+ if (typeof receiver == "bigint")
+ return J.JavaScriptBigInt.prototype;
+ return receiver;
+ }
+ if (receiver instanceof A.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$ax(receiver) {
+ if (receiver == null)
+ return receiver;
+ if (Array.isArray(receiver))
+ return J.JSArray.prototype;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ if (typeof receiver == "symbol")
+ return J.JavaScriptSymbol.prototype;
+ if (typeof receiver == "bigint")
+ return J.JavaScriptBigInt.prototype;
+ return receiver;
+ }
+ if (receiver instanceof A.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$n(receiver) {
+ if (typeof receiver == "number")
+ return J.JSNumber.prototype;
+ if (receiver == null)
+ return receiver;
+ if (!(receiver instanceof A.Object))
+ return J.UnknownJavaScriptObject.prototype;
+ return receiver;
+ },
+ getInterceptor$s(receiver) {
+ if (typeof receiver == "string")
+ return J.JSString.prototype;
+ if (receiver == null)
+ return receiver;
+ if (!(receiver instanceof A.Object))
+ return J.UnknownJavaScriptObject.prototype;
+ return receiver;
+ },
+ getInterceptor$x(receiver) {
+ if (receiver == null)
+ return receiver;
+ if (typeof receiver != "object") {
+ if (typeof receiver == "function")
+ return J.JavaScriptFunction.prototype;
+ if (typeof receiver == "symbol")
+ return J.JavaScriptSymbol.prototype;
+ if (typeof receiver == "bigint")
+ return J.JavaScriptBigInt.prototype;
+ return receiver;
+ }
+ if (receiver instanceof A.Object)
+ return receiver;
+ return J.getNativeInterceptor(receiver);
+ },
+ getInterceptor$z(receiver) {
+ if (receiver == null)
+ return receiver;
+ if (!(receiver instanceof A.Object))
+ return J.UnknownJavaScriptObject.prototype;
+ return receiver;
+ },
+ get$hashCode$(receiver) {
+ return J.getInterceptor$(receiver).get$hashCode(receiver);
+ },
+ get$isEmpty$asx(receiver) {
+ return J.getInterceptor$asx(receiver).get$isEmpty(receiver);
+ },
+ get$isNotEmpty$asx(receiver) {
+ return J.getInterceptor$asx(receiver).get$isNotEmpty(receiver);
+ },
+ get$iterator$ax(receiver) {
+ return J.getInterceptor$ax(receiver).get$iterator(receiver);
+ },
+ get$keys$x(receiver) {
+ return J.getInterceptor$x(receiver).get$keys(receiver);
+ },
+ get$length$asx(receiver) {
+ return J.getInterceptor$asx(receiver).get$length(receiver);
+ },
+ get$parent$z(receiver) {
+ return J.getInterceptor$z(receiver).get$parent(receiver);
+ },
+ get$runtimeType$(receiver) {
+ return J.getInterceptor$(receiver).get$runtimeType(receiver);
+ },
+ $eq$(receiver, a0) {
+ if (receiver == null)
+ return a0 == null;
+ if (typeof receiver != "object")
+ return a0 != null && receiver === a0;
+ return J.getInterceptor$(receiver).$eq(receiver, a0);
+ },
+ $index$asx(receiver, a0) {
+ if (typeof a0 === "number")
+ if (Array.isArray(receiver) || typeof receiver == "string" || A.isJsIndexable(receiver, receiver[init.dispatchPropertyName]))
+ if (a0 >>> 0 === a0 && a0 < receiver.length)
+ return receiver[a0];
+ return J.getInterceptor$asx(receiver).$index(receiver, a0);
+ },
+ $indexSet$ax(receiver, a0, a1) {
+ return J.getInterceptor$ax(receiver).$indexSet(receiver, a0, a1);
+ },
+ allMatches$1$s(receiver, a0) {
+ return J.getInterceptor$s(receiver).allMatches$1(receiver, a0);
+ },
+ allMatches$2$s(receiver, a0, a1) {
+ return J.getInterceptor$s(receiver).allMatches$2(receiver, a0, a1);
+ },
+ cancel$0$z(receiver) {
+ return J.getInterceptor$z(receiver).cancel$0(receiver);
+ },
+ cast$1$0$ax(receiver, $T1) {
+ return J.getInterceptor$ax(receiver).cast$1$0(receiver, $T1);
+ },
+ codeUnitAt$1$s(receiver, a0) {
+ return J.getInterceptor$s(receiver).codeUnitAt$1(receiver, a0);
+ },
+ contains$1$asx(receiver, a0) {
+ return J.getInterceptor$asx(receiver).contains$1(receiver, a0);
+ },
+ containsKey$1$x(receiver, a0) {
+ return J.getInterceptor$x(receiver).containsKey$1(receiver, a0);
+ },
+ elementAt$1$ax(receiver, a0) {
+ return J.getInterceptor$ax(receiver).elementAt$1(receiver, a0);
+ },
+ endsWith$1$s(receiver, a0) {
+ return J.getInterceptor$s(receiver).endsWith$1(receiver, a0);
+ },
+ forEach$1$x(receiver, a0) {
+ return J.getInterceptor$x(receiver).forEach$1(receiver, a0);
+ },
+ map$1$1$ax(receiver, a0, $T1) {
+ return J.getInterceptor$ax(receiver).map$1$1(receiver, a0, $T1);
+ },
+ matchAsPrefix$2$s(receiver, a0, a1) {
+ return J.getInterceptor$s(receiver).matchAsPrefix$2(receiver, a0, a1);
+ },
+ noSuchMethod$1$(receiver, a0) {
+ return J.getInterceptor$(receiver).noSuchMethod$1(receiver, a0);
+ },
+ skip$1$ax(receiver, a0) {
+ return J.getInterceptor$ax(receiver).skip$1(receiver, a0);
+ },
+ toInt$0$n(receiver) {
+ return J.getInterceptor$n(receiver).toInt$0(receiver);
+ },
+ toList$0$ax(receiver) {
+ return J.getInterceptor$ax(receiver).toList$0(receiver);
+ },
+ toString$0$(receiver) {
+ return J.getInterceptor$(receiver).toString$0(receiver);
+ },
+ Interceptor: function Interceptor() {
+ },
+ JSBool: function JSBool() {
+ },
+ JSNull: function JSNull() {
+ },
+ JavaScriptObject: function JavaScriptObject() {
+ },
+ LegacyJavaScriptObject: function LegacyJavaScriptObject() {
+ },
+ PlainJavaScriptObject: function PlainJavaScriptObject() {
+ },
+ UnknownJavaScriptObject: function UnknownJavaScriptObject() {
+ },
+ JavaScriptFunction: function JavaScriptFunction() {
+ },
+ JavaScriptBigInt: function JavaScriptBigInt() {
+ },
+ JavaScriptSymbol: function JavaScriptSymbol() {
+ },
+ JSArray: function JSArray(t0) {
+ this.$ti = t0;
+ },
+ JSUnmodifiableArray: function JSUnmodifiableArray(t0) {
+ this.$ti = t0;
+ },
+ ArrayIterator: function ArrayIterator(t0, t1, t2) {
+ var _ = this;
+ _._iterable = t0;
+ _.__interceptors$_length = t1;
+ _._index = 0;
+ _._current = null;
+ _.$ti = t2;
+ },
+ JSNumber: function JSNumber() {
+ },
+ JSInt: function JSInt() {
+ },
+ JSNumNotInt: function JSNumNotInt() {
+ },
+ JSString: function JSString() {
+ }
+ },
+ A = {JS_CONST: function JS_CONST() {
+ },
+ CastIterable_CastIterable(source, $S, $T) {
+ if ($S._eval$1("EfficientLengthIterable<0>")._is(source))
+ return new A._EfficientLengthCastIterable(source, $S._eval$1("@<0>")._bind$1($T)._eval$1("_EfficientLengthCastIterable<1,2>"));
+ return new A.CastIterable(source, $S._eval$1("@<0>")._bind$1($T)._eval$1("CastIterable<1,2>"));
+ },
+ hexDigitValue(char) {
+ var letter,
+ digit = char ^ 48;
+ if (digit <= 9)
+ return digit;
+ letter = char | 32;
+ if (97 <= letter && letter <= 102)
+ return letter - 87;
+ return -1;
+ },
+ SystemHash_combine(hash, value) {
+ hash = hash + value & 536870911;
+ hash = hash + ((hash & 524287) << 10) & 536870911;
+ return hash ^ hash >>> 6;
+ },
+ SystemHash_finish(hash) {
+ hash = hash + ((hash & 67108863) << 3) & 536870911;
+ hash ^= hash >>> 11;
+ return hash + ((hash & 16383) << 15) & 536870911;
+ },
+ checkNotNullable(value, $name, $T) {
+ return value;
+ },
+ isToStringVisiting(object) {
+ var t1, i;
+ for (t1 = $.toStringVisiting.length, i = 0; i < t1; ++i)
+ if (object === $.toStringVisiting[i])
+ return true;
+ return false;
+ },
+ SubListIterable$(_iterable, _start, _endOrLength, $E) {
+ A.RangeError_checkNotNegative(_start, "start");
+ if (_endOrLength != null) {
+ A.RangeError_checkNotNegative(_endOrLength, "end");
+ if (_start > _endOrLength)
+ A.throwExpression(A.RangeError$range(_start, 0, _endOrLength, "start", null));
+ }
+ return new A.SubListIterable(_iterable, _start, _endOrLength, $E._eval$1("SubListIterable<0>"));
+ },
+ MappedIterable_MappedIterable(iterable, $function, $S, $T) {
+ if (type$.EfficientLengthIterable_dynamic._is(iterable))
+ return new A.EfficientLengthMappedIterable(iterable, $function, $S._eval$1("@<0>")._bind$1($T)._eval$1("EfficientLengthMappedIterable<1,2>"));
+ return new A.MappedIterable(iterable, $function, $S._eval$1("@<0>")._bind$1($T)._eval$1("MappedIterable<1,2>"));
+ },
+ TakeIterable_TakeIterable(iterable, takeCount, $E) {
+ var _s9_ = "takeCount";
+ A.ArgumentError_checkNotNull(takeCount, _s9_, type$.int);
+ A.RangeError_checkNotNegative(takeCount, _s9_);
+ if (type$.EfficientLengthIterable_dynamic._is(iterable))
+ return new A.EfficientLengthTakeIterable(iterable, takeCount, $E._eval$1("EfficientLengthTakeIterable<0>"));
+ return new A.TakeIterable(iterable, takeCount, $E._eval$1("TakeIterable<0>"));
+ },
+ SkipIterable_SkipIterable(iterable, count, $E) {
+ var _s5_ = "count";
+ if (type$.EfficientLengthIterable_dynamic._is(iterable)) {
+ A.ArgumentError_checkNotNull(count, _s5_, type$.int);
+ A.RangeError_checkNotNegative(count, _s5_);
+ return new A.EfficientLengthSkipIterable(iterable, count, $E._eval$1("EfficientLengthSkipIterable<0>"));
+ }
+ A.ArgumentError_checkNotNull(count, _s5_, type$.int);
+ A.RangeError_checkNotNegative(count, _s5_);
+ return new A.SkipIterable(iterable, count, $E._eval$1("SkipIterable<0>"));
+ },
+ IterableElementError_noElement() {
+ return new A.StateError("No element");
+ },
+ IterableElementError_tooFew() {
+ return new A.StateError("Too few elements");
+ },
+ CastStream: function CastStream(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ CastStreamSubscription: function CastStreamSubscription(t0, t1, t2) {
+ var _ = this;
+ _._source = t0;
+ _.__internal$_zone = t1;
+ _._handleError = _._handleData = null;
+ _.$ti = t2;
+ },
+ _CastIterableBase: function _CastIterableBase() {
+ },
+ CastIterator: function CastIterator(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ CastIterable: function CastIterable(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ _EfficientLengthCastIterable: function _EfficientLengthCastIterable(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ _CastListBase: function _CastListBase() {
+ },
+ CastList: function CastList(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ LateError: function LateError(t0) {
+ this._message = t0;
+ },
+ CodeUnits: function CodeUnits(t0) {
+ this._string = t0;
+ },
+ nullFuture_closure: function nullFuture_closure() {
+ },
+ SentinelValue: function SentinelValue() {
+ },
+ EfficientLengthIterable: function EfficientLengthIterable() {
+ },
+ ListIterable: function ListIterable() {
+ },
+ SubListIterable: function SubListIterable(t0, t1, t2, t3) {
+ var _ = this;
+ _.__internal$_iterable = t0;
+ _._start = t1;
+ _._endOrLength = t2;
+ _.$ti = t3;
+ },
+ ListIterator: function ListIterator(t0, t1, t2) {
+ var _ = this;
+ _.__internal$_iterable = t0;
+ _.__internal$_length = t1;
+ _.__internal$_index = 0;
+ _.__internal$_current = null;
+ _.$ti = t2;
+ },
+ MappedIterable: function MappedIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ EfficientLengthMappedIterable: function EfficientLengthMappedIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ MappedIterator: function MappedIterator(t0, t1, t2) {
+ var _ = this;
+ _.__internal$_current = null;
+ _._iterator = t0;
+ _._f = t1;
+ _.$ti = t2;
+ },
+ MappedListIterable: function MappedListIterable(t0, t1, t2) {
+ this._source = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ WhereIterable: function WhereIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ WhereIterator: function WhereIterator(t0, t1, t2) {
+ this._iterator = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ ExpandIterable: function ExpandIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ ExpandIterator: function ExpandIterator(t0, t1, t2, t3) {
+ var _ = this;
+ _._iterator = t0;
+ _._f = t1;
+ _._currentExpansion = t2;
+ _.__internal$_current = null;
+ _.$ti = t3;
+ },
+ TakeIterable: function TakeIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._takeCount = t1;
+ this.$ti = t2;
+ },
+ EfficientLengthTakeIterable: function EfficientLengthTakeIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._takeCount = t1;
+ this.$ti = t2;
+ },
+ TakeIterator: function TakeIterator(t0, t1, t2) {
+ this._iterator = t0;
+ this._remaining = t1;
+ this.$ti = t2;
+ },
+ SkipIterable: function SkipIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._skipCount = t1;
+ this.$ti = t2;
+ },
+ EfficientLengthSkipIterable: function EfficientLengthSkipIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._skipCount = t1;
+ this.$ti = t2;
+ },
+ SkipIterator: function SkipIterator(t0, t1, t2) {
+ this._iterator = t0;
+ this._skipCount = t1;
+ this.$ti = t2;
+ },
+ SkipWhileIterable: function SkipWhileIterable(t0, t1, t2) {
+ this.__internal$_iterable = t0;
+ this._f = t1;
+ this.$ti = t2;
+ },
+ SkipWhileIterator: function SkipWhileIterator(t0, t1, t2) {
+ var _ = this;
+ _._iterator = t0;
+ _._f = t1;
+ _._hasSkipped = false;
+ _.$ti = t2;
+ },
+ EmptyIterable: function EmptyIterable(t0) {
+ this.$ti = t0;
+ },
+ EmptyIterator: function EmptyIterator(t0) {
+ this.$ti = t0;
+ },
+ WhereTypeIterable: function WhereTypeIterable(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ WhereTypeIterator: function WhereTypeIterator(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ FixedLengthListMixin: function FixedLengthListMixin() {
+ },
+ UnmodifiableListMixin: function UnmodifiableListMixin() {
+ },
+ UnmodifiableListBase: function UnmodifiableListBase() {
+ },
+ ReversedListIterable: function ReversedListIterable(t0, t1) {
+ this._source = t0;
+ this.$ti = t1;
+ },
+ Symbol: function Symbol(t0) {
+ this._name = t0;
+ },
+ __CastListBase__CastIterableBase_ListMixin: function __CastListBase__CastIterableBase_ListMixin() {
+ },
+ ConstantMap__throwUnmodifiable() {
+ throw A.wrapException(A.UnsupportedError$("Cannot modify unmodifiable Map"));
+ },
+ unminifyOrTag(rawClassName) {
+ var preserved = init.mangledGlobalNames[rawClassName];
+ if (preserved != null)
+ return preserved;
+ return rawClassName;
+ },
+ isJsIndexable(object, record) {
+ var result;
+ if (record != null) {
+ result = record.x;
+ if (result != null)
+ return result;
+ }
+ return type$.JavaScriptIndexingBehavior_dynamic._is(object);
+ },
+ S(value) {
+ var result;
+ if (typeof value == "string")
+ return value;
+ if (typeof value == "number") {
+ if (value !== 0)
+ return "" + value;
+ } else if (true === value)
+ return "true";
+ else if (false === value)
+ return "false";
+ else if (value == null)
+ return "null";
+ result = J.toString$0$(value);
+ return result;
+ },
+ Primitives_objectHashCode(object) {
+ var hash,
+ property = $.Primitives__identityHashCodeProperty;
+ if (property == null)
+ property = $.Primitives__identityHashCodeProperty = Symbol("identityHashCode");
+ hash = object[property];
+ if (hash == null) {
+ hash = Math.random() * 0x3fffffff | 0;
+ object[property] = hash;
+ }
+ return hash;
+ },
+ Primitives_parseInt(source, radix) {
+ var decimalMatch, maxCharCode, digitsPart, t1, i, _null = null,
+ match = /^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i.exec(source);
+ if (match == null)
+ return _null;
+ if (3 >= match.length)
+ return A.ioore(match, 3);
+ decimalMatch = match[3];
+ if (radix == null) {
+ if (decimalMatch != null)
+ return parseInt(source, 10);
+ if (match[2] != null)
+ return parseInt(source, 16);
+ return _null;
+ }
+ if (radix < 2 || radix > 36)
+ throw A.wrapException(A.RangeError$range(radix, 2, 36, "radix", _null));
+ if (radix === 10 && decimalMatch != null)
+ return parseInt(source, 10);
+ if (radix < 10 || decimalMatch == null) {
+ maxCharCode = radix <= 10 ? 47 + radix : 86 + radix;
+ digitsPart = match[1];
+ for (t1 = digitsPart.length, i = 0; i < t1; ++i)
+ if ((digitsPart.charCodeAt(i) | 32) > maxCharCode)
+ return _null;
+ }
+ return parseInt(source, radix);
+ },
+ Primitives_objectTypeName(object) {
+ return A.Primitives__objectTypeNameNewRti(object);
+ },
+ Primitives__objectTypeNameNewRti(object) {
+ var interceptor, dispatchName, $constructor, constructorName;
+ if (object instanceof A.Object)
+ return A._rtiToString(A.instanceType(object), null);
+ interceptor = J.getInterceptor$(object);
+ if (interceptor === B.Interceptor_methods || interceptor === B.JavaScriptObject_methods || type$.UnknownJavaScriptObject._is(object)) {
+ dispatchName = B.C_JS_CONST(object);
+ if (dispatchName !== "Object" && dispatchName !== "")
+ return dispatchName;
+ $constructor = object.constructor;
+ if (typeof $constructor == "function") {
+ constructorName = $constructor.name;
+ if (typeof constructorName == "string" && constructorName !== "Object" && constructorName !== "")
+ return constructorName;
+ }
+ }
+ return A._rtiToString(A.instanceType(object), null);
+ },
+ Primitives_safeToString(object) {
+ if (typeof object == "number" || A._isBool(object))
+ return J.toString$0$(object);
+ if (typeof object == "string")
+ return JSON.stringify(object);
+ if (object instanceof A.Closure)
+ return object.toString$0(0);
+ return "Instance of '" + A.Primitives_objectTypeName(object) + "'";
+ },
+ Primitives_currentUri() {
+ if (!!self.location)
+ return self.location.href;
+ return null;
+ },
+ Primitives__fromCharCodeApply(array) {
+ var result, i, i0, chunkEnd,
+ end = array.length;
+ if (end <= 500)
+ return String.fromCharCode.apply(null, array);
+ for (result = "", i = 0; i < end; i = i0) {
+ i0 = i + 500;
+ chunkEnd = i0 < end ? i0 : end;
+ result += String.fromCharCode.apply(null, array.slice(i, chunkEnd));
+ }
+ return result;
+ },
+ Primitives_stringFromCodePoints(codePoints) {
+ var t1, _i, i,
+ a = A._setArrayType([], type$.JSArray_int);
+ for (t1 = codePoints.length, _i = 0; _i < codePoints.length; codePoints.length === t1 || (0, A.throwConcurrentModificationError)(codePoints), ++_i) {
+ i = codePoints[_i];
+ if (!A._isInt(i))
+ throw A.wrapException(A.argumentErrorValue(i));
+ if (i <= 65535)
+ B.JSArray_methods.add$1(a, i);
+ else if (i <= 1114111) {
+ B.JSArray_methods.add$1(a, 55296 + (B.JSInt_methods._shrOtherPositive$1(i - 65536, 10) & 1023));
+ B.JSArray_methods.add$1(a, 56320 + (i & 1023));
+ } else
+ throw A.wrapException(A.argumentErrorValue(i));
+ }
+ return A.Primitives__fromCharCodeApply(a);
+ },
+ Primitives_stringFromCharCodes(charCodes) {
+ var t1, _i, i;
+ for (t1 = charCodes.length, _i = 0; _i < t1; ++_i) {
+ i = charCodes[_i];
+ if (!A._isInt(i))
+ throw A.wrapException(A.argumentErrorValue(i));
+ if (i < 0)
+ throw A.wrapException(A.argumentErrorValue(i));
+ if (i > 65535)
+ return A.Primitives_stringFromCodePoints(charCodes);
+ }
+ return A.Primitives__fromCharCodeApply(charCodes);
+ },
+ Primitives_stringFromNativeUint8List(charCodes, start, end) {
+ var i, result, i0, chunkEnd;
+ if (end <= 500 && start === 0 && end === charCodes.length)
+ return String.fromCharCode.apply(null, charCodes);
+ for (i = start, result = ""; i < end; i = i0) {
+ i0 = i + 500;
+ chunkEnd = i0 < end ? i0 : end;
+ result += String.fromCharCode.apply(null, charCodes.subarray(i, chunkEnd));
+ }
+ return result;
+ },
+ Primitives_stringFromCharCode(charCode) {
+ var bits;
+ if (0 <= charCode) {
+ if (charCode <= 65535)
+ return String.fromCharCode(charCode);
+ if (charCode <= 1114111) {
+ bits = charCode - 65536;
+ return String.fromCharCode((B.JSInt_methods._shrOtherPositive$1(bits, 10) | 55296) >>> 0, bits & 1023 | 56320);
+ }
+ }
+ throw A.wrapException(A.RangeError$range(charCode, 0, 1114111, null, null));
+ },
+ Primitives_lazyAsJsDate(receiver) {
+ if (receiver.date === void 0)
+ receiver.date = new Date(receiver._core$_value);
+ return receiver.date;
+ },
+ Primitives_getYear(receiver) {
+ var t1 = A.Primitives_lazyAsJsDate(receiver).getUTCFullYear() + 0;
+ return t1;
+ },
+ Primitives_getMonth(receiver) {
+ var t1 = A.Primitives_lazyAsJsDate(receiver).getUTCMonth() + 1;
+ return t1;
+ },
+ Primitives_getDay(receiver) {
+ var t1 = A.Primitives_lazyAsJsDate(receiver).getUTCDate() + 0;
+ return t1;
+ },
+ Primitives_getHours(receiver) {
+ var t1 = A.Primitives_lazyAsJsDate(receiver).getUTCHours() + 0;
+ return t1;
+ },
+ Primitives_getMinutes(receiver) {
+ var t1 = A.Primitives_lazyAsJsDate(receiver).getUTCMinutes() + 0;
+ return t1;
+ },
+ Primitives_getSeconds(receiver) {
+ var t1 = A.Primitives_lazyAsJsDate(receiver).getUTCSeconds() + 0;
+ return t1;
+ },
+ Primitives_getMilliseconds(receiver) {
+ var t1 = A.Primitives_lazyAsJsDate(receiver).getUTCMilliseconds() + 0;
+ return t1;
+ },
+ Primitives_functionNoSuchMethod($function, positionalArguments, namedArguments) {
+ var $arguments, namedArgumentList, t1 = {};
+ t1.argumentCount = 0;
+ $arguments = [];
+ namedArgumentList = [];
+ t1.argumentCount = positionalArguments.length;
+ B.JSArray_methods.addAll$1($arguments, positionalArguments);
+ t1.names = "";
+ if (namedArguments != null && namedArguments._length !== 0)
+ namedArguments.forEach$1(0, new A.Primitives_functionNoSuchMethod_closure(t1, namedArgumentList, $arguments));
+ return J.noSuchMethod$1$($function, new A.JSInvocationMirror(B.Symbol_call, 0, $arguments, namedArgumentList, 0));
+ },
+ Primitives_applyFunction($function, positionalArguments, namedArguments) {
+ var t1, argumentCount, jsStub;
+ if (Array.isArray(positionalArguments))
+ t1 = namedArguments == null || namedArguments._length === 0;
+ else
+ t1 = false;
+ if (t1) {
+ argumentCount = positionalArguments.length;
+ if (argumentCount === 0) {
+ if (!!$function.call$0)
+ return $function.call$0();
+ } else if (argumentCount === 1) {
+ if (!!$function.call$1)
+ return $function.call$1(positionalArguments[0]);
+ } else if (argumentCount === 2) {
+ if (!!$function.call$2)
+ return $function.call$2(positionalArguments[0], positionalArguments[1]);
+ } else if (argumentCount === 3) {
+ if (!!$function.call$3)
+ return $function.call$3(positionalArguments[0], positionalArguments[1], positionalArguments[2]);
+ } else if (argumentCount === 4) {
+ if (!!$function.call$4)
+ return $function.call$4(positionalArguments[0], positionalArguments[1], positionalArguments[2], positionalArguments[3]);
+ } else if (argumentCount === 5)
+ if (!!$function.call$5)
+ return $function.call$5(positionalArguments[0], positionalArguments[1], positionalArguments[2], positionalArguments[3], positionalArguments[4]);
+ jsStub = $function["call" + "$" + argumentCount];
+ if (jsStub != null)
+ return jsStub.apply($function, positionalArguments);
+ }
+ return A.Primitives__generalApplyFunction($function, positionalArguments, namedArguments);
+ },
+ Primitives__generalApplyFunction($function, positionalArguments, namedArguments) {
+ var defaultValuesClosure, t1, defaultValues, interceptor, jsFunction, maxArguments, missingDefaults, keys, _i, defaultValue, used, key,
+ $arguments = Array.isArray(positionalArguments) ? positionalArguments : A.List_List$of(positionalArguments, true, type$.dynamic),
+ argumentCount = $arguments.length,
+ requiredParameterCount = $function.$requiredArgCount;
+ if (argumentCount < requiredParameterCount)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ defaultValuesClosure = $function.$defaultValues;
+ t1 = defaultValuesClosure == null;
+ defaultValues = !t1 ? defaultValuesClosure() : null;
+ interceptor = J.getInterceptor$($function);
+ jsFunction = interceptor["call*"];
+ if (typeof jsFunction == "string")
+ jsFunction = interceptor[jsFunction];
+ if (t1) {
+ if (namedArguments != null && namedArguments._length !== 0)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ if (argumentCount === requiredParameterCount)
+ return jsFunction.apply($function, $arguments);
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ }
+ if (Array.isArray(defaultValues)) {
+ if (namedArguments != null && namedArguments._length !== 0)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ maxArguments = requiredParameterCount + defaultValues.length;
+ if (argumentCount > maxArguments)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, null);
+ if (argumentCount < maxArguments) {
+ missingDefaults = defaultValues.slice(argumentCount - requiredParameterCount);
+ if ($arguments === positionalArguments)
+ $arguments = A.List_List$of($arguments, true, type$.dynamic);
+ B.JSArray_methods.addAll$1($arguments, missingDefaults);
+ }
+ return jsFunction.apply($function, $arguments);
+ } else {
+ if (argumentCount > requiredParameterCount)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ if ($arguments === positionalArguments)
+ $arguments = A.List_List$of($arguments, true, type$.dynamic);
+ keys = Object.keys(defaultValues);
+ if (namedArguments == null)
+ for (t1 = keys.length, _i = 0; _i < keys.length; keys.length === t1 || (0, A.throwConcurrentModificationError)(keys), ++_i) {
+ defaultValue = defaultValues[A._asString(keys[_i])];
+ if (B.C__Required === defaultValue)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ B.JSArray_methods.add$1($arguments, defaultValue);
+ }
+ else {
+ for (t1 = keys.length, used = 0, _i = 0; _i < keys.length; keys.length === t1 || (0, A.throwConcurrentModificationError)(keys), ++_i) {
+ key = A._asString(keys[_i]);
+ if (namedArguments.containsKey$1(0, key)) {
+ ++used;
+ B.JSArray_methods.add$1($arguments, namedArguments.$index(0, key));
+ } else {
+ defaultValue = defaultValues[key];
+ if (B.C__Required === defaultValue)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ B.JSArray_methods.add$1($arguments, defaultValue);
+ }
+ }
+ if (used !== namedArguments._length)
+ return A.Primitives_functionNoSuchMethod($function, $arguments, namedArguments);
+ }
+ return jsFunction.apply($function, $arguments);
+ }
+ },
+ iae(argument) {
+ throw A.wrapException(A.argumentErrorValue(argument));
+ },
+ ioore(receiver, index) {
+ if (receiver == null)
+ J.get$length$asx(receiver);
+ throw A.wrapException(A.diagnoseIndexError(receiver, index));
+ },
+ diagnoseIndexError(indexable, index) {
+ var $length, _s5_ = "index";
+ if (!A._isInt(index))
+ return new A.ArgumentError(true, index, _s5_, null);
+ $length = A._asInt(J.get$length$asx(indexable));
+ if (index < 0 || index >= $length)
+ return A.IndexError$withLength(index, $length, indexable, _s5_);
+ return A.RangeError$value(index, _s5_);
+ },
+ diagnoseRangeError(start, end, $length) {
+ if (start > $length)
+ return A.RangeError$range(start, 0, $length, "start", null);
+ if (end != null)
+ if (end < start || end > $length)
+ return A.RangeError$range(end, start, $length, "end", null);
+ return new A.ArgumentError(true, end, "end", null);
+ },
+ argumentErrorValue(object) {
+ return new A.ArgumentError(true, object, null, null);
+ },
+ wrapException(ex) {
+ return A.initializeExceptionWrapper(new Error(), ex);
+ },
+ initializeExceptionWrapper(wrapper, ex) {
+ var t1;
+ if (ex == null)
+ ex = new A.TypeError();
+ wrapper.dartException = ex;
+ t1 = A.toStringWrapper;
+ if ("defineProperty" in Object) {
+ Object.defineProperty(wrapper, "message", {get: t1});
+ wrapper.name = "";
+ } else
+ wrapper.toString = t1;
+ return wrapper;
+ },
+ toStringWrapper() {
+ return J.toString$0$(this.dartException);
+ },
+ throwExpression(ex) {
+ throw A.wrapException(ex);
+ },
+ throwExpressionWithWrapper(ex, wrapper) {
+ throw A.initializeExceptionWrapper(wrapper, ex);
+ },
+ throwConcurrentModificationError(collection) {
+ throw A.wrapException(A.ConcurrentModificationError$(collection));
+ },
+ TypeErrorDecoder_extractPattern(message) {
+ var match, $arguments, argumentsExpr, expr, method, receiver;
+ message = A.quoteStringForRegExp(message.replace(String({}), "$receiver$"));
+ match = message.match(/\\\$[a-zA-Z]+\\\$/g);
+ if (match == null)
+ match = A._setArrayType([], type$.JSArray_String);
+ $arguments = match.indexOf("\\$arguments\\$");
+ argumentsExpr = match.indexOf("\\$argumentsExpr\\$");
+ expr = match.indexOf("\\$expr\\$");
+ method = match.indexOf("\\$method\\$");
+ receiver = match.indexOf("\\$receiver\\$");
+ return new A.TypeErrorDecoder(message.replace(new RegExp("\\\\\\$arguments\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$argumentsExpr\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$expr\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$method\\\\\\$", "g"), "((?:x|[^x])*)").replace(new RegExp("\\\\\\$receiver\\\\\\$", "g"), "((?:x|[^x])*)"), $arguments, argumentsExpr, expr, method, receiver);
+ },
+ TypeErrorDecoder_provokeCallErrorOn(expression) {
+ return function($expr$) {
+ var $argumentsExpr$ = "$arguments$";
+ try {
+ $expr$.$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }(expression);
+ },
+ TypeErrorDecoder_provokePropertyErrorOn(expression) {
+ return function($expr$) {
+ try {
+ $expr$.$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }(expression);
+ },
+ JsNoSuchMethodError$(_message, match) {
+ var t1 = match == null,
+ t2 = t1 ? null : match.method;
+ return new A.JsNoSuchMethodError(_message, t2, t1 ? null : match.receiver);
+ },
+ unwrapException(ex) {
+ if (ex == null)
+ return new A.NullThrownFromJavaScriptException(ex);
+ if (typeof ex !== "object")
+ return ex;
+ if ("dartException" in ex)
+ return A.saveStackTrace(ex, ex.dartException);
+ return A._unwrapNonDartException(ex);
+ },
+ saveStackTrace(ex, error) {
+ if (type$.Error._is(error))
+ if (error.$thrownJsError == null)
+ error.$thrownJsError = ex;
+ return error;
+ },
+ _unwrapNonDartException(ex) {
+ var message, number, ieErrorCode, nsme, notClosure, nullCall, nullLiteralCall, undefCall, undefLiteralCall, nullProperty, undefProperty, undefLiteralProperty, match;
+ if (!("message" in ex))
+ return ex;
+ message = ex.message;
+ if ("number" in ex && typeof ex.number == "number") {
+ number = ex.number;
+ ieErrorCode = number & 65535;
+ if ((B.JSInt_methods._shrOtherPositive$1(number, 16) & 8191) === 10)
+ switch (ieErrorCode) {
+ case 438:
+ return A.saveStackTrace(ex, A.JsNoSuchMethodError$(A.S(message) + " (Error " + ieErrorCode + ")", null));
+ case 445:
+ case 5007:
+ A.S(message);
+ return A.saveStackTrace(ex, new A.NullError());
+ }
+ }
+ if (ex instanceof TypeError) {
+ nsme = $.$get$TypeErrorDecoder_noSuchMethodPattern();
+ notClosure = $.$get$TypeErrorDecoder_notClosurePattern();
+ nullCall = $.$get$TypeErrorDecoder_nullCallPattern();
+ nullLiteralCall = $.$get$TypeErrorDecoder_nullLiteralCallPattern();
+ undefCall = $.$get$TypeErrorDecoder_undefinedCallPattern();
+ undefLiteralCall = $.$get$TypeErrorDecoder_undefinedLiteralCallPattern();
+ nullProperty = $.$get$TypeErrorDecoder_nullPropertyPattern();
+ $.$get$TypeErrorDecoder_nullLiteralPropertyPattern();
+ undefProperty = $.$get$TypeErrorDecoder_undefinedPropertyPattern();
+ undefLiteralProperty = $.$get$TypeErrorDecoder_undefinedLiteralPropertyPattern();
+ match = nsme.matchTypeError$1(message);
+ if (match != null)
+ return A.saveStackTrace(ex, A.JsNoSuchMethodError$(A._asString(message), match));
+ else {
+ match = notClosure.matchTypeError$1(message);
+ if (match != null) {
+ match.method = "call";
+ return A.saveStackTrace(ex, A.JsNoSuchMethodError$(A._asString(message), match));
+ } else if (nullCall.matchTypeError$1(message) != null || nullLiteralCall.matchTypeError$1(message) != null || undefCall.matchTypeError$1(message) != null || undefLiteralCall.matchTypeError$1(message) != null || nullProperty.matchTypeError$1(message) != null || nullLiteralCall.matchTypeError$1(message) != null || undefProperty.matchTypeError$1(message) != null || undefLiteralProperty.matchTypeError$1(message) != null) {
+ A._asString(message);
+ return A.saveStackTrace(ex, new A.NullError());
+ }
+ }
+ return A.saveStackTrace(ex, new A.UnknownJsTypeError(typeof message == "string" ? message : ""));
+ }
+ if (ex instanceof RangeError) {
+ if (typeof message == "string" && message.indexOf("call stack") !== -1)
+ return new A.StackOverflowError();
+ message = function(ex) {
+ try {
+ return String(ex);
+ } catch (e) {
+ }
+ return null;
+ }(ex);
+ return A.saveStackTrace(ex, new A.ArgumentError(false, null, null, typeof message == "string" ? message.replace(/^RangeError:\s*/, "") : message));
+ }
+ if (typeof InternalError == "function" && ex instanceof InternalError)
+ if (typeof message == "string" && message === "too much recursion")
+ return new A.StackOverflowError();
+ return ex;
+ },
+ getTraceFromException(exception) {
+ var trace;
+ if (exception == null)
+ return new A._StackTrace(exception);
+ trace = exception.$cachedTrace;
+ if (trace != null)
+ return trace;
+ trace = new A._StackTrace(exception);
+ if (typeof exception === "object")
+ exception.$cachedTrace = trace;
+ return trace;
+ },
+ objectHashCode(object) {
+ if (object == null)
+ return J.get$hashCode$(object);
+ if (typeof object == "object")
+ return A.Primitives_objectHashCode(object);
+ return J.get$hashCode$(object);
+ },
+ fillLiteralMap(keyValuePairs, result) {
+ var index, index0, index1,
+ $length = keyValuePairs.length;
+ for (index = 0; index < $length; index = index1) {
+ index0 = index + 1;
+ index1 = index0 + 1;
+ result.$indexSet(0, keyValuePairs[index], keyValuePairs[index0]);
+ }
+ return result;
+ },
+ _invokeClosure(closure, numberOfArguments, arg1, arg2, arg3, arg4) {
+ type$.Function._as(closure);
+ switch (A._asInt(numberOfArguments)) {
+ case 0:
+ return closure.call$0();
+ case 1:
+ return closure.call$1(arg1);
+ case 2:
+ return closure.call$2(arg1, arg2);
+ case 3:
+ return closure.call$3(arg1, arg2, arg3);
+ case 4:
+ return closure.call$4(arg1, arg2, arg3, arg4);
+ }
+ throw A.wrapException(new A._Exception("Unsupported number of arguments for wrapped closure"));
+ },
+ convertDartClosureToJS(closure, arity) {
+ var $function = closure.$identity;
+ if (!!$function)
+ return $function;
+ $function = A.convertDartClosureToJSUncached(closure, arity);
+ closure.$identity = $function;
+ return $function;
+ },
+ convertDartClosureToJSUncached(closure, arity) {
+ var entry;
+ switch (arity) {
+ case 0:
+ entry = closure.call$0;
+ break;
+ case 1:
+ entry = closure.call$1;
+ break;
+ case 2:
+ entry = closure.call$2;
+ break;
+ case 3:
+ entry = closure.call$3;
+ break;
+ case 4:
+ entry = closure.call$4;
+ break;
+ default:
+ entry = null;
+ }
+ if (entry != null)
+ return entry.bind(closure);
+ return function(closure, arity, invoke) {
+ return function(a1, a2, a3, a4) {
+ return invoke(closure, arity, a1, a2, a3, a4);
+ };
+ }(closure, arity, A._invokeClosure);
+ },
+ Closure_fromTearOff(parameters) {
+ var $prototype, $constructor, t2, trampoline, applyTrampoline, i, stub, stub0, stubName, stubCallName,
+ container = parameters.co,
+ isStatic = parameters.iS,
+ isIntercepted = parameters.iI,
+ needsDirectAccess = parameters.nDA,
+ applyTrampolineIndex = parameters.aI,
+ funsOrNames = parameters.fs,
+ callNames = parameters.cs,
+ $name = funsOrNames[0],
+ callName = callNames[0],
+ $function = container[$name],
+ t1 = parameters.fT;
+ t1.toString;
+ $prototype = isStatic ? Object.create(new A.StaticClosure().constructor.prototype) : Object.create(new A.BoundClosure(null, null).constructor.prototype);
+ $prototype.$initialize = $prototype.constructor;
+ if (isStatic)
+ $constructor = function static_tear_off() {
+ this.$initialize();
+ };
+ else
+ $constructor = function tear_off(a, b) {
+ this.$initialize(a, b);
+ };
+ $prototype.constructor = $constructor;
+ $constructor.prototype = $prototype;
+ $prototype.$_name = $name;
+ $prototype.$_target = $function;
+ t2 = !isStatic;
+ if (t2)
+ trampoline = A.Closure_forwardCallTo($name, $function, isIntercepted, needsDirectAccess);
+ else {
+ $prototype.$static_name = $name;
+ trampoline = $function;
+ }
+ $prototype.$signature = A.Closure__computeSignatureFunctionNewRti(t1, isStatic, isIntercepted);
+ $prototype[callName] = trampoline;
+ for (applyTrampoline = trampoline, i = 1; i < funsOrNames.length; ++i) {
+ stub = funsOrNames[i];
+ if (typeof stub == "string") {
+ stub0 = container[stub];
+ stubName = stub;
+ stub = stub0;
+ } else
+ stubName = "";
+ stubCallName = callNames[i];
+ if (stubCallName != null) {
+ if (t2)
+ stub = A.Closure_forwardCallTo(stubName, stub, isIntercepted, needsDirectAccess);
+ $prototype[stubCallName] = stub;
+ }
+ if (i === applyTrampolineIndex)
+ applyTrampoline = stub;
+ }
+ $prototype["call*"] = applyTrampoline;
+ $prototype.$requiredArgCount = parameters.rC;
+ $prototype.$defaultValues = parameters.dV;
+ return $constructor;
+ },
+ Closure__computeSignatureFunctionNewRti(functionType, isStatic, isIntercepted) {
+ if (typeof functionType == "number")
+ return functionType;
+ if (typeof functionType == "string") {
+ if (isStatic)
+ throw A.wrapException("Cannot compute signature for static tearoff.");
+ return function(recipe, evalOnReceiver) {
+ return function() {
+ return evalOnReceiver(this, recipe);
+ };
+ }(functionType, A.BoundClosure_evalRecipe);
+ }
+ throw A.wrapException("Error in functionType of tearoff");
+ },
+ Closure_cspForwardCall(arity, needsDirectAccess, stubName, $function) {
+ var getReceiver = A.BoundClosure_receiverOf;
+ switch (needsDirectAccess ? -1 : arity) {
+ case 0:
+ return function(entry, receiverOf) {
+ return function() {
+ return receiverOf(this)[entry]();
+ };
+ }(stubName, getReceiver);
+ case 1:
+ return function(entry, receiverOf) {
+ return function(a) {
+ return receiverOf(this)[entry](a);
+ };
+ }(stubName, getReceiver);
+ case 2:
+ return function(entry, receiverOf) {
+ return function(a, b) {
+ return receiverOf(this)[entry](a, b);
+ };
+ }(stubName, getReceiver);
+ case 3:
+ return function(entry, receiverOf) {
+ return function(a, b, c) {
+ return receiverOf(this)[entry](a, b, c);
+ };
+ }(stubName, getReceiver);
+ case 4:
+ return function(entry, receiverOf) {
+ return function(a, b, c, d) {
+ return receiverOf(this)[entry](a, b, c, d);
+ };
+ }(stubName, getReceiver);
+ case 5:
+ return function(entry, receiverOf) {
+ return function(a, b, c, d, e) {
+ return receiverOf(this)[entry](a, b, c, d, e);
+ };
+ }(stubName, getReceiver);
+ default:
+ return function(f, receiverOf) {
+ return function() {
+ return f.apply(receiverOf(this), arguments);
+ };
+ }($function, getReceiver);
+ }
+ },
+ Closure_forwardCallTo(stubName, $function, isIntercepted, needsDirectAccess) {
+ var arity, t1;
+ if (isIntercepted)
+ return A.Closure_forwardInterceptedCallTo(stubName, $function, needsDirectAccess);
+ arity = $function.length;
+ t1 = A.Closure_cspForwardCall(arity, needsDirectAccess, stubName, $function);
+ return t1;
+ },
+ Closure_cspForwardInterceptedCall(arity, needsDirectAccess, stubName, $function) {
+ var getReceiver = A.BoundClosure_receiverOf,
+ getInterceptor = A.BoundClosure_interceptorOf;
+ switch (needsDirectAccess ? -1 : arity) {
+ case 0:
+ throw A.wrapException(new A.RuntimeError("Intercepted function with no arguments."));
+ case 1:
+ return function(entry, interceptorOf, receiverOf) {
+ return function() {
+ return interceptorOf(this)[entry](receiverOf(this));
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 2:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a) {
+ return interceptorOf(this)[entry](receiverOf(this), a);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 3:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 4:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b, c) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b, c);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 5:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b, c, d) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b, c, d);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ case 6:
+ return function(entry, interceptorOf, receiverOf) {
+ return function(a, b, c, d, e) {
+ return interceptorOf(this)[entry](receiverOf(this), a, b, c, d, e);
+ };
+ }(stubName, getInterceptor, getReceiver);
+ default:
+ return function(f, interceptorOf, receiverOf) {
+ return function() {
+ var a = [receiverOf(this)];
+ Array.prototype.push.apply(a, arguments);
+ return f.apply(interceptorOf(this), a);
+ };
+ }($function, getInterceptor, getReceiver);
+ }
+ },
+ Closure_forwardInterceptedCallTo(stubName, $function, needsDirectAccess) {
+ var arity, t1;
+ if ($.BoundClosure__interceptorFieldNameCache == null)
+ $.BoundClosure__interceptorFieldNameCache = A.BoundClosure__computeFieldNamed("interceptor");
+ if ($.BoundClosure__receiverFieldNameCache == null)
+ $.BoundClosure__receiverFieldNameCache = A.BoundClosure__computeFieldNamed("receiver");
+ arity = $function.length;
+ t1 = A.Closure_cspForwardInterceptedCall(arity, needsDirectAccess, stubName, $function);
+ return t1;
+ },
+ closureFromTearOff(parameters) {
+ return A.Closure_fromTearOff(parameters);
+ },
+ BoundClosure_evalRecipe(closure, recipe) {
+ return A._Universe_evalInEnvironment(init.typeUniverse, A.instanceType(closure._receiver), recipe);
+ },
+ BoundClosure_receiverOf(closure) {
+ return closure._receiver;
+ },
+ BoundClosure_interceptorOf(closure) {
+ return closure._interceptor;
+ },
+ BoundClosure__computeFieldNamed(fieldName) {
+ var t1, i, $name,
+ template = new A.BoundClosure("receiver", "interceptor"),
+ names = J.JSArray_markFixedList(Object.getOwnPropertyNames(template), type$.nullable_Object);
+ for (t1 = names.length, i = 0; i < t1; ++i) {
+ $name = names[i];
+ if (template[$name] === fieldName)
+ return $name;
+ }
+ throw A.wrapException(A.ArgumentError$("Field name " + fieldName + " not found.", null));
+ },
+ boolConversionCheck(value) {
+ if (value == null)
+ A.assertThrow("boolean expression must not be null");
+ return value;
+ },
+ assertThrow(message) {
+ throw A.wrapException(new A._AssertionError(message));
+ },
+ throwCyclicInit(staticName) {
+ throw A.wrapException(new A._CyclicInitializationError(staticName));
+ },
+ getIsolateAffinityTag($name) {
+ return init.getIsolateTag($name);
+ },
+ defineProperty(obj, property, value) {
+ Object.defineProperty(obj, property, {value: value, enumerable: false, writable: true, configurable: true});
+ },
+ lookupAndCacheInterceptor(obj) {
+ var interceptor, interceptorClass, altTag, mark, t1,
+ tag = A._asString($.getTagFunction.call$1(obj)),
+ record = $.dispatchRecordsForInstanceTags[tag];
+ if (record != null) {
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ interceptor = $.interceptorsForUncacheableTags[tag];
+ if (interceptor != null)
+ return interceptor;
+ interceptorClass = init.interceptorsByTag[tag];
+ if (interceptorClass == null) {
+ altTag = A._asStringQ($.alternateTagFunction.call$2(obj, tag));
+ if (altTag != null) {
+ record = $.dispatchRecordsForInstanceTags[altTag];
+ if (record != null) {
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ interceptor = $.interceptorsForUncacheableTags[altTag];
+ if (interceptor != null)
+ return interceptor;
+ interceptorClass = init.interceptorsByTag[altTag];
+ tag = altTag;
+ }
+ }
+ if (interceptorClass == null)
+ return null;
+ interceptor = interceptorClass.prototype;
+ mark = tag[0];
+ if (mark === "!") {
+ record = A.makeLeafDispatchRecord(interceptor);
+ $.dispatchRecordsForInstanceTags[tag] = record;
+ Object.defineProperty(obj, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ return record.i;
+ }
+ if (mark === "~") {
+ $.interceptorsForUncacheableTags[tag] = interceptor;
+ return interceptor;
+ }
+ if (mark === "-") {
+ t1 = A.makeLeafDispatchRecord(interceptor);
+ Object.defineProperty(Object.getPrototypeOf(obj), init.dispatchPropertyName, {value: t1, enumerable: false, writable: true, configurable: true});
+ return t1.i;
+ }
+ if (mark === "+")
+ return A.patchInteriorProto(obj, interceptor);
+ if (mark === "*")
+ throw A.wrapException(A.UnimplementedError$(tag));
+ if (init.leafTags[tag] === true) {
+ t1 = A.makeLeafDispatchRecord(interceptor);
+ Object.defineProperty(Object.getPrototypeOf(obj), init.dispatchPropertyName, {value: t1, enumerable: false, writable: true, configurable: true});
+ return t1.i;
+ } else
+ return A.patchInteriorProto(obj, interceptor);
+ },
+ patchInteriorProto(obj, interceptor) {
+ var proto = Object.getPrototypeOf(obj);
+ Object.defineProperty(proto, init.dispatchPropertyName, {value: J.makeDispatchRecord(interceptor, proto, null, null), enumerable: false, writable: true, configurable: true});
+ return interceptor;
+ },
+ makeLeafDispatchRecord(interceptor) {
+ return J.makeDispatchRecord(interceptor, false, null, !!interceptor.$isJavaScriptIndexingBehavior);
+ },
+ makeDefaultDispatchRecord(tag, interceptorClass, proto) {
+ var interceptor = interceptorClass.prototype;
+ if (init.leafTags[tag] === true)
+ return A.makeLeafDispatchRecord(interceptor);
+ else
+ return J.makeDispatchRecord(interceptor, proto, null, null);
+ },
+ initNativeDispatch() {
+ if (true === $.initNativeDispatchFlag)
+ return;
+ $.initNativeDispatchFlag = true;
+ A.initNativeDispatchContinue();
+ },
+ initNativeDispatchContinue() {
+ var map, tags, fun, i, tag, proto, record, interceptorClass;
+ $.dispatchRecordsForInstanceTags = Object.create(null);
+ $.interceptorsForUncacheableTags = Object.create(null);
+ A.initHooks();
+ map = init.interceptorsByTag;
+ tags = Object.getOwnPropertyNames(map);
+ if (typeof window != "undefined") {
+ window;
+ fun = function() {
+ };
+ for (i = 0; i < tags.length; ++i) {
+ tag = tags[i];
+ proto = $.prototypeForTagFunction.call$1(tag);
+ if (proto != null) {
+ record = A.makeDefaultDispatchRecord(tag, map[tag], proto);
+ if (record != null) {
+ Object.defineProperty(proto, init.dispatchPropertyName, {value: record, enumerable: false, writable: true, configurable: true});
+ fun.prototype = proto;
+ }
+ }
+ }
+ }
+ for (i = 0; i < tags.length; ++i) {
+ tag = tags[i];
+ if (/^[A-Za-z_]/.test(tag)) {
+ interceptorClass = map[tag];
+ map["!" + tag] = interceptorClass;
+ map["~" + tag] = interceptorClass;
+ map["-" + tag] = interceptorClass;
+ map["+" + tag] = interceptorClass;
+ map["*" + tag] = interceptorClass;
+ }
+ }
+ },
+ initHooks() {
+ var transformers, i, transformer, getTag, getUnknownTag, prototypeForTag,
+ hooks = B.C_JS_CONST0();
+ hooks = A.applyHooksTransformer(B.C_JS_CONST1, A.applyHooksTransformer(B.C_JS_CONST2, A.applyHooksTransformer(B.C_JS_CONST3, A.applyHooksTransformer(B.C_JS_CONST3, A.applyHooksTransformer(B.C_JS_CONST4, A.applyHooksTransformer(B.C_JS_CONST5, A.applyHooksTransformer(B.C_JS_CONST6(B.C_JS_CONST), hooks)))))));
+ if (typeof dartNativeDispatchHooksTransformer != "undefined") {
+ transformers = dartNativeDispatchHooksTransformer;
+ if (typeof transformers == "function")
+ transformers = [transformers];
+ if (Array.isArray(transformers))
+ for (i = 0; i < transformers.length; ++i) {
+ transformer = transformers[i];
+ if (typeof transformer == "function")
+ hooks = transformer(hooks) || hooks;
+ }
+ }
+ getTag = hooks.getTag;
+ getUnknownTag = hooks.getUnknownTag;
+ prototypeForTag = hooks.prototypeForTag;
+ $.getTagFunction = new A.initHooks_closure(getTag);
+ $.alternateTagFunction = new A.initHooks_closure0(getUnknownTag);
+ $.prototypeForTagFunction = new A.initHooks_closure1(prototypeForTag);
+ },
+ applyHooksTransformer(transformer, hooks) {
+ return transformer(hooks) || hooks;
+ },
+ createRecordTypePredicate(shape, fieldRtis) {
+ var $length = fieldRtis.length,
+ $function = init.rttc["" + $length + ";" + shape];
+ if ($function == null)
+ return null;
+ if ($length === 0)
+ return $function;
+ if ($length === $function.length)
+ return $function.apply(null, fieldRtis);
+ return $function(fieldRtis);
+ },
+ JSSyntaxRegExp_makeNative(source, multiLine, caseSensitive, unicode, dotAll, global) {
+ var m = multiLine ? "m" : "",
+ i = caseSensitive ? "" : "i",
+ u = unicode ? "u" : "",
+ s = dotAll ? "s" : "",
+ g = global ? "g" : "",
+ regexp = function(source, modifiers) {
+ try {
+ return new RegExp(source, modifiers);
+ } catch (e) {
+ return e;
+ }
+ }(source, m + i + u + s + g);
+ if (regexp instanceof RegExp)
+ return regexp;
+ throw A.wrapException(A.FormatException$("Illegal RegExp pattern (" + String(regexp) + ")", source, null));
+ },
+ stringContainsUnchecked(receiver, other, startIndex) {
+ var t1;
+ if (typeof other == "string")
+ return receiver.indexOf(other, startIndex) >= 0;
+ else if (other instanceof A.JSSyntaxRegExp) {
+ t1 = B.JSString_methods.substring$1(receiver, startIndex);
+ return other._nativeRegExp.test(t1);
+ } else {
+ t1 = J.allMatches$1$s(other, B.JSString_methods.substring$1(receiver, startIndex));
+ return !t1.get$isEmpty(t1);
+ }
+ },
+ escapeReplacement(replacement) {
+ if (replacement.indexOf("$", 0) >= 0)
+ return replacement.replace(/\$/g, "$$$$");
+ return replacement;
+ },
+ stringReplaceFirstRE(receiver, regexp, replacement, startIndex) {
+ var match = regexp._execGlobal$2(receiver, startIndex);
+ if (match == null)
+ return receiver;
+ return A.stringReplaceRangeUnchecked(receiver, match._match.index, match.get$end(match), replacement);
+ },
+ quoteStringForRegExp(string) {
+ if (/[[\]{}()*+?.\\^$|]/.test(string))
+ return string.replace(/[[\]{}()*+?.\\^$|]/g, "\\$&");
+ return string;
+ },
+ stringReplaceAllUnchecked(receiver, pattern, replacement) {
+ var nativeRegexp;
+ if (typeof pattern == "string")
+ return A.stringReplaceAllUncheckedString(receiver, pattern, replacement);
+ if (pattern instanceof A.JSSyntaxRegExp) {
+ nativeRegexp = pattern.get$_nativeGlobalVersion();
+ nativeRegexp.lastIndex = 0;
+ return receiver.replace(nativeRegexp, A.escapeReplacement(replacement));
+ }
+ return A.stringReplaceAllGeneral(receiver, pattern, replacement);
+ },
+ stringReplaceAllGeneral(receiver, pattern, replacement) {
+ var t1, startIndex, t2, match;
+ for (t1 = J.allMatches$1$s(pattern, receiver), t1 = t1.get$iterator(t1), startIndex = 0, t2 = ""; t1.moveNext$0();) {
+ match = t1.get$current(t1);
+ t2 = t2 + receiver.substring(startIndex, match.get$start(match)) + replacement;
+ startIndex = match.get$end(match);
+ }
+ t1 = t2 + receiver.substring(startIndex);
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ stringReplaceAllUncheckedString(receiver, pattern, replacement) {
+ var $length, t1, i;
+ if (pattern === "") {
+ if (receiver === "")
+ return replacement;
+ $length = receiver.length;
+ t1 = "" + replacement;
+ for (i = 0; i < $length; ++i)
+ t1 = t1 + receiver[i] + replacement;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ if (receiver.indexOf(pattern, 0) < 0)
+ return receiver;
+ if (receiver.length < 500 || replacement.indexOf("$", 0) >= 0)
+ return receiver.split(pattern).join(replacement);
+ return receiver.replace(new RegExp(A.quoteStringForRegExp(pattern), "g"), A.escapeReplacement(replacement));
+ },
+ stringReplaceFirstUnchecked(receiver, pattern, replacement, startIndex) {
+ var index, t1, matches, match;
+ if (typeof pattern == "string") {
+ index = receiver.indexOf(pattern, startIndex);
+ if (index < 0)
+ return receiver;
+ return A.stringReplaceRangeUnchecked(receiver, index, index + pattern.length, replacement);
+ }
+ if (pattern instanceof A.JSSyntaxRegExp)
+ return startIndex === 0 ? receiver.replace(pattern._nativeRegExp, A.escapeReplacement(replacement)) : A.stringReplaceFirstRE(receiver, pattern, replacement, startIndex);
+ t1 = J.allMatches$2$s(pattern, receiver, startIndex);
+ matches = t1.get$iterator(t1);
+ if (!matches.moveNext$0())
+ return receiver;
+ match = matches.get$current(matches);
+ return B.JSString_methods.replaceRange$3(receiver, match.get$start(match), match.get$end(match), replacement);
+ },
+ stringReplaceRangeUnchecked(receiver, start, end, replacement) {
+ return receiver.substring(0, start) + replacement + receiver.substring(end);
+ },
+ ConstantMapView: function ConstantMapView(t0, t1) {
+ this._collection$_map = t0;
+ this.$ti = t1;
+ },
+ ConstantMap: function ConstantMap() {
+ },
+ ConstantStringMap: function ConstantStringMap(t0, t1, t2) {
+ this._jsIndex = t0;
+ this._values = t1;
+ this.$ti = t2;
+ },
+ _KeysOrValues: function _KeysOrValues(t0, t1) {
+ this._elements = t0;
+ this.$ti = t1;
+ },
+ _KeysOrValuesOrElementsIterator: function _KeysOrValuesOrElementsIterator(t0, t1, t2) {
+ var _ = this;
+ _._elements = t0;
+ _._length = t1;
+ _.__js_helper$_index = 0;
+ _.__js_helper$_current = null;
+ _.$ti = t2;
+ },
+ Instantiation: function Instantiation() {
+ },
+ Instantiation1: function Instantiation1(t0, t1) {
+ this._genericClosure = t0;
+ this.$ti = t1;
+ },
+ JSInvocationMirror: function JSInvocationMirror(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._memberName = t0;
+ _.__js_helper$_kind = t1;
+ _._arguments = t2;
+ _._namedArgumentNames = t3;
+ _._typeArgumentCount = t4;
+ },
+ Primitives_functionNoSuchMethod_closure: function Primitives_functionNoSuchMethod_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.namedArgumentList = t1;
+ this.$arguments = t2;
+ },
+ TypeErrorDecoder: function TypeErrorDecoder(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _._pattern = t0;
+ _._arguments = t1;
+ _._argumentsExpr = t2;
+ _._expr = t3;
+ _._method = t4;
+ _._receiver = t5;
+ },
+ NullError: function NullError() {
+ },
+ JsNoSuchMethodError: function JsNoSuchMethodError(t0, t1, t2) {
+ this.__js_helper$_message = t0;
+ this._method = t1;
+ this._receiver = t2;
+ },
+ UnknownJsTypeError: function UnknownJsTypeError(t0) {
+ this.__js_helper$_message = t0;
+ },
+ NullThrownFromJavaScriptException: function NullThrownFromJavaScriptException(t0) {
+ this._irritant = t0;
+ },
+ _StackTrace: function _StackTrace(t0) {
+ this._exception = t0;
+ this._trace = null;
+ },
+ Closure: function Closure() {
+ },
+ Closure0Args: function Closure0Args() {
+ },
+ Closure2Args: function Closure2Args() {
+ },
+ TearOffClosure: function TearOffClosure() {
+ },
+ StaticClosure: function StaticClosure() {
+ },
+ BoundClosure: function BoundClosure(t0, t1) {
+ this._receiver = t0;
+ this._interceptor = t1;
+ },
+ _CyclicInitializationError: function _CyclicInitializationError(t0) {
+ this.variableName = t0;
+ },
+ RuntimeError: function RuntimeError(t0) {
+ this.message = t0;
+ },
+ _AssertionError: function _AssertionError(t0) {
+ this.message = t0;
+ },
+ _Required: function _Required() {
+ },
+ JsLinkedHashMap: function JsLinkedHashMap(t0) {
+ var _ = this;
+ _._length = 0;
+ _._last = _._first = _.__js_helper$_rest = _._nums = _._strings = null;
+ _._modifications = 0;
+ _.$ti = t0;
+ },
+ JsLinkedHashMap_values_closure: function JsLinkedHashMap_values_closure(t0) {
+ this.$this = t0;
+ },
+ LinkedHashMapCell: function LinkedHashMapCell(t0, t1) {
+ var _ = this;
+ _.hashMapCellKey = t0;
+ _.hashMapCellValue = t1;
+ _._previous = _._next = null;
+ },
+ LinkedHashMapKeyIterable: function LinkedHashMapKeyIterable(t0, t1) {
+ this._map = t0;
+ this.$ti = t1;
+ },
+ LinkedHashMapKeyIterator: function LinkedHashMapKeyIterator(t0, t1, t2) {
+ var _ = this;
+ _._map = t0;
+ _._modifications = t1;
+ _.__js_helper$_current = _._cell = null;
+ _.$ti = t2;
+ },
+ initHooks_closure: function initHooks_closure(t0) {
+ this.getTag = t0;
+ },
+ initHooks_closure0: function initHooks_closure0(t0) {
+ this.getUnknownTag = t0;
+ },
+ initHooks_closure1: function initHooks_closure1(t0) {
+ this.prototypeForTag = t0;
+ },
+ JSSyntaxRegExp: function JSSyntaxRegExp(t0, t1) {
+ var _ = this;
+ _.pattern = t0;
+ _._nativeRegExp = t1;
+ _._nativeAnchoredRegExp = _._nativeGlobalRegExp = null;
+ },
+ _MatchImplementation: function _MatchImplementation(t0) {
+ this._match = t0;
+ },
+ _AllMatchesIterable: function _AllMatchesIterable(t0, t1, t2) {
+ this._re = t0;
+ this.__js_helper$_string = t1;
+ this.__js_helper$_start = t2;
+ },
+ _AllMatchesIterator: function _AllMatchesIterator(t0, t1, t2) {
+ var _ = this;
+ _._regExp = t0;
+ _.__js_helper$_string = t1;
+ _._nextIndex = t2;
+ _.__js_helper$_current = null;
+ },
+ StringMatch: function StringMatch(t0, t1) {
+ this.start = t0;
+ this.pattern = t1;
+ },
+ _StringAllMatchesIterable: function _StringAllMatchesIterable(t0, t1, t2) {
+ this._input = t0;
+ this._pattern = t1;
+ this.__js_helper$_index = t2;
+ },
+ _StringAllMatchesIterator: function _StringAllMatchesIterator(t0, t1, t2) {
+ var _ = this;
+ _._input = t0;
+ _._pattern = t1;
+ _.__js_helper$_index = t2;
+ _.__js_helper$_current = null;
+ },
+ throwLateFieldNI(fieldName) {
+ A.throwExpressionWithWrapper(new A.LateError("Field '" + fieldName + "' has not been initialized."), new Error());
+ },
+ throwLateFieldAI(fieldName) {
+ A.throwExpressionWithWrapper(new A.LateError("Field '" + fieldName + "' has already been initialized."), new Error());
+ },
+ throwLateFieldADI(fieldName) {
+ A.throwExpressionWithWrapper(new A.LateError("Field '" + fieldName + string$.x27_has_), new Error());
+ },
+ _Cell$named(_name) {
+ var t1 = new A._Cell(_name);
+ return t1._value = t1;
+ },
+ _InitializedCell$named(_name, _initializer) {
+ var t1 = new A._InitializedCell(_name, _initializer);
+ return t1._value = t1;
+ },
+ _Cell: function _Cell(t0) {
+ this.__late_helper$_name = t0;
+ this._value = null;
+ },
+ _InitializedCell: function _InitializedCell(t0, t1) {
+ this.__late_helper$_name = t0;
+ this._value = null;
+ this._initializer = t1;
+ },
+ _ensureNativeList(list) {
+ return list;
+ },
+ NativeInt8List__create1(arg) {
+ return new Int8Array(arg);
+ },
+ _checkValidIndex(index, list, $length) {
+ if (index >>> 0 !== index || index >= $length)
+ throw A.wrapException(A.diagnoseIndexError(list, index));
+ },
+ _checkValidRange(start, end, $length) {
+ var t1;
+ if (!(start >>> 0 !== start))
+ t1 = end >>> 0 !== end || start > end || end > $length;
+ else
+ t1 = true;
+ if (t1)
+ throw A.wrapException(A.diagnoseRangeError(start, end, $length));
+ return end;
+ },
+ NativeByteBuffer: function NativeByteBuffer() {
+ },
+ NativeTypedData: function NativeTypedData() {
+ },
+ NativeByteData: function NativeByteData() {
+ },
+ NativeTypedArray: function NativeTypedArray() {
+ },
+ NativeTypedArrayOfDouble: function NativeTypedArrayOfDouble() {
+ },
+ NativeTypedArrayOfInt: function NativeTypedArrayOfInt() {
+ },
+ NativeFloat32List: function NativeFloat32List() {
+ },
+ NativeFloat64List: function NativeFloat64List() {
+ },
+ NativeInt16List: function NativeInt16List() {
+ },
+ NativeInt32List: function NativeInt32List() {
+ },
+ NativeInt8List: function NativeInt8List() {
+ },
+ NativeUint16List: function NativeUint16List() {
+ },
+ NativeUint32List: function NativeUint32List() {
+ },
+ NativeUint8ClampedList: function NativeUint8ClampedList() {
+ },
+ NativeUint8List: function NativeUint8List() {
+ },
+ _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin: function _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin() {
+ },
+ _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin: function _NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin() {
+ },
+ _NativeTypedArrayOfInt_NativeTypedArray_ListMixin: function _NativeTypedArrayOfInt_NativeTypedArray_ListMixin() {
+ },
+ _NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin: function _NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin() {
+ },
+ Rti__getQuestionFromStar(universe, rti) {
+ var question = rti._precomputed1;
+ return question == null ? rti._precomputed1 = A._Universe__lookupQuestionRti(universe, rti._primary, true) : question;
+ },
+ Rti__getFutureFromFutureOr(universe, rti) {
+ var future = rti._precomputed1;
+ return future == null ? rti._precomputed1 = A._Universe__lookupInterfaceRti(universe, "Future", [rti._primary]) : future;
+ },
+ Rti__getIsSubtypeCache(rti) {
+ var probe = rti._isSubtypeCache;
+ if (probe != null)
+ return probe;
+ return rti._isSubtypeCache = new Map();
+ },
+ Rti__isUnionOfFunctionType(rti) {
+ var kind = rti._kind;
+ if (kind === 6 || kind === 7 || kind === 8)
+ return A.Rti__isUnionOfFunctionType(rti._primary);
+ return kind === 12 || kind === 13;
+ },
+ Rti__getCanonicalRecipe(rti) {
+ return rti._canonicalRecipe;
+ },
+ findType(recipe) {
+ return A._Universe_eval(init.typeUniverse, recipe, false);
+ },
+ instantiatedGenericFunctionType(genericFunctionRti, instantiationRti) {
+ var t1, cache, key, probe, rti;
+ if (genericFunctionRti == null)
+ return null;
+ t1 = instantiationRti._rest;
+ cache = genericFunctionRti._bindCache;
+ if (cache == null)
+ cache = genericFunctionRti._bindCache = new Map();
+ key = instantiationRti._canonicalRecipe;
+ probe = cache.get(key);
+ if (probe != null)
+ return probe;
+ rti = A._substitute(init.typeUniverse, genericFunctionRti._primary, t1, 0);
+ cache.set(key, rti);
+ return rti;
+ },
+ _substitute(universe, rti, typeArguments, depth) {
+ var baseType, substitutedBaseType, interfaceTypeArguments, substitutedInterfaceTypeArguments, base, substitutedBase, $arguments, substitutedArguments, returnType, substitutedReturnType, functionParameters, substitutedFunctionParameters, bounds, substitutedBounds, index, argument,
+ kind = rti._kind;
+ switch (kind) {
+ case 5:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return rti;
+ case 6:
+ baseType = rti._primary;
+ substitutedBaseType = A._substitute(universe, baseType, typeArguments, depth);
+ if (substitutedBaseType === baseType)
+ return rti;
+ return A._Universe__lookupStarRti(universe, substitutedBaseType, true);
+ case 7:
+ baseType = rti._primary;
+ substitutedBaseType = A._substitute(universe, baseType, typeArguments, depth);
+ if (substitutedBaseType === baseType)
+ return rti;
+ return A._Universe__lookupQuestionRti(universe, substitutedBaseType, true);
+ case 8:
+ baseType = rti._primary;
+ substitutedBaseType = A._substitute(universe, baseType, typeArguments, depth);
+ if (substitutedBaseType === baseType)
+ return rti;
+ return A._Universe__lookupFutureOrRti(universe, substitutedBaseType, true);
+ case 9:
+ interfaceTypeArguments = rti._rest;
+ substitutedInterfaceTypeArguments = A._substituteArray(universe, interfaceTypeArguments, typeArguments, depth);
+ if (substitutedInterfaceTypeArguments === interfaceTypeArguments)
+ return rti;
+ return A._Universe__lookupInterfaceRti(universe, rti._primary, substitutedInterfaceTypeArguments);
+ case 10:
+ base = rti._primary;
+ substitutedBase = A._substitute(universe, base, typeArguments, depth);
+ $arguments = rti._rest;
+ substitutedArguments = A._substituteArray(universe, $arguments, typeArguments, depth);
+ if (substitutedBase === base && substitutedArguments === $arguments)
+ return rti;
+ return A._Universe__lookupBindingRti(universe, substitutedBase, substitutedArguments);
+ case 12:
+ returnType = rti._primary;
+ substitutedReturnType = A._substitute(universe, returnType, typeArguments, depth);
+ functionParameters = rti._rest;
+ substitutedFunctionParameters = A._substituteFunctionParameters(universe, functionParameters, typeArguments, depth);
+ if (substitutedReturnType === returnType && substitutedFunctionParameters === functionParameters)
+ return rti;
+ return A._Universe__lookupFunctionRti(universe, substitutedReturnType, substitutedFunctionParameters);
+ case 13:
+ bounds = rti._rest;
+ depth += bounds.length;
+ substitutedBounds = A._substituteArray(universe, bounds, typeArguments, depth);
+ base = rti._primary;
+ substitutedBase = A._substitute(universe, base, typeArguments, depth);
+ if (substitutedBounds === bounds && substitutedBase === base)
+ return rti;
+ return A._Universe__lookupGenericFunctionRti(universe, substitutedBase, substitutedBounds, true);
+ case 14:
+ index = rti._primary;
+ if (index < depth)
+ return rti;
+ argument = typeArguments[index - depth];
+ if (argument == null)
+ return rti;
+ return argument;
+ default:
+ throw A.wrapException(A.AssertionError$("Attempted to substitute unexpected RTI kind " + kind));
+ }
+ },
+ _substituteArray(universe, rtiArray, typeArguments, depth) {
+ var changed, i, rti, substitutedRti,
+ $length = rtiArray.length,
+ result = A._Utils_newArrayOrEmpty($length);
+ for (changed = false, i = 0; i < $length; ++i) {
+ rti = rtiArray[i];
+ substitutedRti = A._substitute(universe, rti, typeArguments, depth);
+ if (substitutedRti !== rti)
+ changed = true;
+ result[i] = substitutedRti;
+ }
+ return changed ? result : rtiArray;
+ },
+ _substituteNamed(universe, namedArray, typeArguments, depth) {
+ var changed, i, t1, t2, rti, substitutedRti,
+ $length = namedArray.length,
+ result = A._Utils_newArrayOrEmpty($length);
+ for (changed = false, i = 0; i < $length; i += 3) {
+ t1 = namedArray[i];
+ t2 = namedArray[i + 1];
+ rti = namedArray[i + 2];
+ substitutedRti = A._substitute(universe, rti, typeArguments, depth);
+ if (substitutedRti !== rti)
+ changed = true;
+ result.splice(i, 3, t1, t2, substitutedRti);
+ }
+ return changed ? result : namedArray;
+ },
+ _substituteFunctionParameters(universe, functionParameters, typeArguments, depth) {
+ var result,
+ requiredPositional = functionParameters._requiredPositional,
+ substitutedRequiredPositional = A._substituteArray(universe, requiredPositional, typeArguments, depth),
+ optionalPositional = functionParameters._optionalPositional,
+ substitutedOptionalPositional = A._substituteArray(universe, optionalPositional, typeArguments, depth),
+ named = functionParameters._named,
+ substitutedNamed = A._substituteNamed(universe, named, typeArguments, depth);
+ if (substitutedRequiredPositional === requiredPositional && substitutedOptionalPositional === optionalPositional && substitutedNamed === named)
+ return functionParameters;
+ result = new A._FunctionParameters();
+ result._requiredPositional = substitutedRequiredPositional;
+ result._optionalPositional = substitutedOptionalPositional;
+ result._named = substitutedNamed;
+ return result;
+ },
+ _setArrayType(target, rti) {
+ target[init.arrayRti] = rti;
+ return target;
+ },
+ closureFunctionType(closure) {
+ var t1,
+ signature = closure.$signature;
+ if (signature != null) {
+ if (typeof signature == "number")
+ return A.getTypeFromTypesTable(signature);
+ t1 = closure.$signature();
+ return t1;
+ }
+ return null;
+ },
+ instanceOrFunctionType(object, testRti) {
+ var rti;
+ if (A.Rti__isUnionOfFunctionType(testRti))
+ if (object instanceof A.Closure) {
+ rti = A.closureFunctionType(object);
+ if (rti != null)
+ return rti;
+ }
+ return A.instanceType(object);
+ },
+ instanceType(object) {
+ if (object instanceof A.Object)
+ return A._instanceType(object);
+ if (Array.isArray(object))
+ return A._arrayInstanceType(object);
+ return A._instanceTypeFromConstructor(J.getInterceptor$(object));
+ },
+ _arrayInstanceType(object) {
+ var rti = object[init.arrayRti],
+ defaultRti = type$.JSArray_dynamic;
+ if (rti == null)
+ return defaultRti;
+ if (rti.constructor !== defaultRti.constructor)
+ return defaultRti;
+ return rti;
+ },
+ _instanceType(object) {
+ var rti = object.$ti;
+ return rti != null ? rti : A._instanceTypeFromConstructor(object);
+ },
+ _instanceTypeFromConstructor(instance) {
+ var $constructor = instance.constructor,
+ probe = $constructor.$ccache;
+ if (probe != null)
+ return probe;
+ return A._instanceTypeFromConstructorMiss(instance, $constructor);
+ },
+ _instanceTypeFromConstructorMiss(instance, $constructor) {
+ var effectiveConstructor = instance instanceof A.Closure ? Object.getPrototypeOf(Object.getPrototypeOf(instance)).constructor : $constructor,
+ rti = A._Universe_findErasedType(init.typeUniverse, effectiveConstructor.name);
+ $constructor.$ccache = rti;
+ return rti;
+ },
+ getTypeFromTypesTable(index) {
+ var rti,
+ table = init.types,
+ type = table[index];
+ if (typeof type == "string") {
+ rti = A._Universe_eval(init.typeUniverse, type, false);
+ table[index] = rti;
+ return rti;
+ }
+ return type;
+ },
+ getRuntimeTypeOfDartObject(object) {
+ return A.createRuntimeType(A._instanceType(object));
+ },
+ getRuntimeTypeOfClosure(closure) {
+ var rti = A.closureFunctionType(closure);
+ return A.createRuntimeType(rti == null ? A.instanceType(closure) : rti);
+ },
+ _structuralTypeOf(object) {
+ var functionRti = object instanceof A.Closure ? A.closureFunctionType(object) : null;
+ if (functionRti != null)
+ return functionRti;
+ if (type$.TrustedGetRuntimeType._is(object))
+ return J.get$runtimeType$(object)._rti;
+ if (Array.isArray(object))
+ return A._arrayInstanceType(object);
+ return A.instanceType(object);
+ },
+ createRuntimeType(rti) {
+ var t1 = rti._cachedRuntimeType;
+ return t1 == null ? rti._cachedRuntimeType = A._createRuntimeType(rti) : t1;
+ },
+ _createRuntimeType(rti) {
+ var starErasedRti, t1,
+ s = rti._canonicalRecipe,
+ starErasedRecipe = s.replace(/\*/g, "");
+ if (starErasedRecipe === s)
+ return rti._cachedRuntimeType = new A._Type(rti);
+ starErasedRti = A._Universe_eval(init.typeUniverse, starErasedRecipe, true);
+ t1 = starErasedRti._cachedRuntimeType;
+ return t1 == null ? starErasedRti._cachedRuntimeType = A._createRuntimeType(starErasedRti) : t1;
+ },
+ typeLiteral(recipe) {
+ return A.createRuntimeType(A._Universe_eval(init.typeUniverse, recipe, false));
+ },
+ _installSpecializedIsTest(object) {
+ var t1, unstarred, unstarredKind, isFn, $name, predicate, testRti = this;
+ if (testRti === type$.Object)
+ return A._finishIsFn(testRti, object, A._isObject);
+ if (!A.isStrongTopType(testRti))
+ if (!(testRti === type$.legacy_Object))
+ t1 = false;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ if (t1)
+ return A._finishIsFn(testRti, object, A._isTop);
+ t1 = testRti._kind;
+ if (t1 === 7)
+ return A._finishIsFn(testRti, object, A._generalNullableIsTestImplementation);
+ if (t1 === 1)
+ return A._finishIsFn(testRti, object, A._isNever);
+ unstarred = t1 === 6 ? testRti._primary : testRti;
+ unstarredKind = unstarred._kind;
+ if (unstarredKind === 8)
+ return A._finishIsFn(testRti, object, A._isFutureOr);
+ if (unstarred === type$.int)
+ isFn = A._isInt;
+ else if (unstarred === type$.double || unstarred === type$.num)
+ isFn = A._isNum;
+ else if (unstarred === type$.String)
+ isFn = A._isString;
+ else
+ isFn = unstarred === type$.bool ? A._isBool : null;
+ if (isFn != null)
+ return A._finishIsFn(testRti, object, isFn);
+ if (unstarredKind === 9) {
+ $name = unstarred._primary;
+ if (unstarred._rest.every(A.isTopType)) {
+ testRti._specializedTestResource = "$is" + $name;
+ if ($name === "List")
+ return A._finishIsFn(testRti, object, A._isListTestViaProperty);
+ return A._finishIsFn(testRti, object, A._isTestViaProperty);
+ }
+ } else if (unstarredKind === 11) {
+ predicate = A.createRecordTypePredicate(unstarred._primary, unstarred._rest);
+ return A._finishIsFn(testRti, object, predicate == null ? A._isNever : predicate);
+ }
+ return A._finishIsFn(testRti, object, A._generalIsTestImplementation);
+ },
+ _finishIsFn(testRti, object, isFn) {
+ testRti._is = isFn;
+ return testRti._is(object);
+ },
+ _installSpecializedAsCheck(object) {
+ var t1, testRti = this,
+ asFn = A._generalAsCheckImplementation;
+ if (!A.isStrongTopType(testRti))
+ if (!(testRti === type$.legacy_Object))
+ t1 = false;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ if (t1)
+ asFn = A._asTop;
+ else if (testRti === type$.Object)
+ asFn = A._asObject;
+ else {
+ t1 = A.isNullable(testRti);
+ if (t1)
+ asFn = A._generalNullableAsCheckImplementation;
+ }
+ testRti._as = asFn;
+ return testRti._as(object);
+ },
+ _nullIs(testRti) {
+ var t1,
+ kind = testRti._kind;
+ if (!A.isStrongTopType(testRti))
+ if (!(testRti === type$.legacy_Object))
+ if (!(testRti === type$.legacy_Never))
+ if (kind !== 7)
+ if (!(kind === 6 && A._nullIs(testRti._primary)))
+ t1 = kind === 8 && A._nullIs(testRti._primary) || testRti === type$.Null || testRti === type$.JSNull;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ return t1;
+ },
+ _generalIsTestImplementation(object) {
+ var testRti = this;
+ if (object == null)
+ return A._nullIs(testRti);
+ return A.isSubtype(init.typeUniverse, A.instanceOrFunctionType(object, testRti), testRti);
+ },
+ _generalNullableIsTestImplementation(object) {
+ if (object == null)
+ return true;
+ return this._primary._is(object);
+ },
+ _isTestViaProperty(object) {
+ var tag, testRti = this;
+ if (object == null)
+ return A._nullIs(testRti);
+ tag = testRti._specializedTestResource;
+ if (object instanceof A.Object)
+ return !!object[tag];
+ return !!J.getInterceptor$(object)[tag];
+ },
+ _isListTestViaProperty(object) {
+ var tag, testRti = this;
+ if (object == null)
+ return A._nullIs(testRti);
+ if (typeof object != "object")
+ return false;
+ if (Array.isArray(object))
+ return true;
+ tag = testRti._specializedTestResource;
+ if (object instanceof A.Object)
+ return !!object[tag];
+ return !!J.getInterceptor$(object)[tag];
+ },
+ _generalAsCheckImplementation(object) {
+ var t1, testRti = this;
+ if (object == null) {
+ t1 = A.isNullable(testRti);
+ if (t1)
+ return object;
+ } else if (testRti._is(object))
+ return object;
+ A._failedAsCheck(object, testRti);
+ },
+ _generalNullableAsCheckImplementation(object) {
+ var testRti = this;
+ if (object == null)
+ return object;
+ else if (testRti._is(object))
+ return object;
+ A._failedAsCheck(object, testRti);
+ },
+ _failedAsCheck(object, testRti) {
+ throw A.wrapException(A._TypeError$fromMessage(A._Error_compose(object, A._rtiToString(testRti, null))));
+ },
+ checkTypeBound(type, bound, variable, methodName) {
+ if (A.isSubtype(init.typeUniverse, type, bound))
+ return type;
+ throw A.wrapException(A._TypeError$fromMessage("The type argument '" + A._rtiToString(type, null) + "' is not a subtype of the type variable bound '" + A._rtiToString(bound, null) + "' of type variable '" + variable + "' in '" + methodName + "'."));
+ },
+ _Error_compose(object, checkedTypeDescription) {
+ return A.Error_safeToString(object) + ": type '" + A._rtiToString(A._structuralTypeOf(object), null) + "' is not a subtype of type '" + checkedTypeDescription + "'";
+ },
+ _TypeError$fromMessage(message) {
+ return new A._TypeError("TypeError: " + message);
+ },
+ _TypeError__TypeError$forType(object, type) {
+ return new A._TypeError("TypeError: " + A._Error_compose(object, type));
+ },
+ _isFutureOr(object) {
+ var testRti = this,
+ unstarred = testRti._kind === 6 ? testRti._primary : testRti;
+ return unstarred._primary._is(object) || A.Rti__getFutureFromFutureOr(init.typeUniverse, unstarred)._is(object);
+ },
+ _isObject(object) {
+ return object != null;
+ },
+ _asObject(object) {
+ if (object != null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "Object"));
+ },
+ _isTop(object) {
+ return true;
+ },
+ _asTop(object) {
+ return object;
+ },
+ _isNever(object) {
+ return false;
+ },
+ _isBool(object) {
+ return true === object || false === object;
+ },
+ _asBool(object) {
+ if (true === object)
+ return true;
+ if (false === object)
+ return false;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "bool"));
+ },
+ _asBoolS(object) {
+ if (true === object)
+ return true;
+ if (false === object)
+ return false;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "bool"));
+ },
+ _asBoolQ(object) {
+ if (true === object)
+ return true;
+ if (false === object)
+ return false;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "bool?"));
+ },
+ _asDouble(object) {
+ if (typeof object == "number")
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "double"));
+ },
+ _asDoubleS(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "double"));
+ },
+ _asDoubleQ(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "double?"));
+ },
+ _isInt(object) {
+ return typeof object == "number" && Math.floor(object) === object;
+ },
+ _asInt(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "int"));
+ },
+ _asIntS(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "int"));
+ },
+ _asIntQ(object) {
+ if (typeof object == "number" && Math.floor(object) === object)
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "int?"));
+ },
+ _isNum(object) {
+ return typeof object == "number";
+ },
+ _asNum(object) {
+ if (typeof object == "number")
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "num"));
+ },
+ _asNumS(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "num"));
+ },
+ _asNumQ(object) {
+ if (typeof object == "number")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "num?"));
+ },
+ _isString(object) {
+ return typeof object == "string";
+ },
+ _asString(object) {
+ if (typeof object == "string")
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "String"));
+ },
+ _asStringS(object) {
+ if (typeof object == "string")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "String"));
+ },
+ _asStringQ(object) {
+ if (typeof object == "string")
+ return object;
+ if (object == null)
+ return object;
+ throw A.wrapException(A._TypeError__TypeError$forType(object, "String?"));
+ },
+ _rtiArrayToString(array, genericContext) {
+ var s, sep, i;
+ for (s = "", sep = "", i = 0; i < array.length; ++i, sep = ", ")
+ s += sep + A._rtiToString(array[i], genericContext);
+ return s;
+ },
+ _recordRtiToString(recordType, genericContext) {
+ var fieldCount, names, namesIndex, s, comma, i,
+ partialShape = recordType._primary,
+ fields = recordType._rest;
+ if ("" === partialShape)
+ return "(" + A._rtiArrayToString(fields, genericContext) + ")";
+ fieldCount = fields.length;
+ names = partialShape.split(",");
+ namesIndex = names.length - fieldCount;
+ for (s = "(", comma = "", i = 0; i < fieldCount; ++i, comma = ", ") {
+ s += comma;
+ if (namesIndex === 0)
+ s += "{";
+ s += A._rtiToString(fields[i], genericContext);
+ if (namesIndex >= 0)
+ s += " " + names[namesIndex];
+ ++namesIndex;
+ }
+ return s + "})";
+ },
+ _functionRtiToString(functionType, genericContext, bounds) {
+ var boundsLength, outerContextLength, offset, i, t1, t2, typeParametersText, typeSep, t3, t4, boundRti, kind, parameters, requiredPositional, requiredPositionalLength, optionalPositional, optionalPositionalLength, named, namedLength, returnTypeText, argumentsText, sep, _s2_ = ", ";
+ if (bounds != null) {
+ boundsLength = bounds.length;
+ if (genericContext == null) {
+ genericContext = A._setArrayType([], type$.JSArray_String);
+ outerContextLength = null;
+ } else
+ outerContextLength = genericContext.length;
+ offset = genericContext.length;
+ for (i = boundsLength; i > 0; --i)
+ B.JSArray_methods.add$1(genericContext, "T" + (offset + i));
+ for (t1 = type$.nullable_Object, t2 = type$.legacy_Object, typeParametersText = "<", typeSep = "", i = 0; i < boundsLength; ++i, typeSep = _s2_) {
+ t3 = genericContext.length;
+ t4 = t3 - 1 - i;
+ if (!(t4 >= 0))
+ return A.ioore(genericContext, t4);
+ typeParametersText = B.JSString_methods.$add(typeParametersText + typeSep, genericContext[t4]);
+ boundRti = bounds[i];
+ kind = boundRti._kind;
+ if (!(kind === 2 || kind === 3 || kind === 4 || kind === 5 || boundRti === t1))
+ if (!(boundRti === t2))
+ t3 = false;
+ else
+ t3 = true;
+ else
+ t3 = true;
+ if (!t3)
+ typeParametersText += " extends " + A._rtiToString(boundRti, genericContext);
+ }
+ typeParametersText += ">";
+ } else {
+ typeParametersText = "";
+ outerContextLength = null;
+ }
+ t1 = functionType._primary;
+ parameters = functionType._rest;
+ requiredPositional = parameters._requiredPositional;
+ requiredPositionalLength = requiredPositional.length;
+ optionalPositional = parameters._optionalPositional;
+ optionalPositionalLength = optionalPositional.length;
+ named = parameters._named;
+ namedLength = named.length;
+ returnTypeText = A._rtiToString(t1, genericContext);
+ for (argumentsText = "", sep = "", i = 0; i < requiredPositionalLength; ++i, sep = _s2_)
+ argumentsText += sep + A._rtiToString(requiredPositional[i], genericContext);
+ if (optionalPositionalLength > 0) {
+ argumentsText += sep + "[";
+ for (sep = "", i = 0; i < optionalPositionalLength; ++i, sep = _s2_)
+ argumentsText += sep + A._rtiToString(optionalPositional[i], genericContext);
+ argumentsText += "]";
+ }
+ if (namedLength > 0) {
+ argumentsText += sep + "{";
+ for (sep = "", i = 0; i < namedLength; i += 3, sep = _s2_) {
+ argumentsText += sep;
+ if (named[i + 1])
+ argumentsText += "required ";
+ argumentsText += A._rtiToString(named[i + 2], genericContext) + " " + named[i];
+ }
+ argumentsText += "}";
+ }
+ if (outerContextLength != null) {
+ genericContext.toString;
+ genericContext.length = outerContextLength;
+ }
+ return typeParametersText + "(" + argumentsText + ") => " + returnTypeText;
+ },
+ _rtiToString(rti, genericContext) {
+ var s, questionArgument, argumentKind, $name, $arguments, t1, t2,
+ kind = rti._kind;
+ if (kind === 5)
+ return "erased";
+ if (kind === 2)
+ return "dynamic";
+ if (kind === 3)
+ return "void";
+ if (kind === 1)
+ return "Never";
+ if (kind === 4)
+ return "any";
+ if (kind === 6) {
+ s = A._rtiToString(rti._primary, genericContext);
+ return s;
+ }
+ if (kind === 7) {
+ questionArgument = rti._primary;
+ s = A._rtiToString(questionArgument, genericContext);
+ argumentKind = questionArgument._kind;
+ return (argumentKind === 12 || argumentKind === 13 ? "(" + s + ")" : s) + "?";
+ }
+ if (kind === 8)
+ return "FutureOr<" + A._rtiToString(rti._primary, genericContext) + ">";
+ if (kind === 9) {
+ $name = A._unminifyOrTag(rti._primary);
+ $arguments = rti._rest;
+ return $arguments.length > 0 ? $name + ("<" + A._rtiArrayToString($arguments, genericContext) + ">") : $name;
+ }
+ if (kind === 11)
+ return A._recordRtiToString(rti, genericContext);
+ if (kind === 12)
+ return A._functionRtiToString(rti, genericContext, null);
+ if (kind === 13)
+ return A._functionRtiToString(rti._primary, genericContext, rti._rest);
+ if (kind === 14) {
+ t1 = rti._primary;
+ t2 = genericContext.length;
+ t1 = t2 - 1 - t1;
+ if (!(t1 >= 0 && t1 < t2))
+ return A.ioore(genericContext, t1);
+ return genericContext[t1];
+ }
+ return "?";
+ },
+ _unminifyOrTag(rawClassName) {
+ var preserved = init.mangledGlobalNames[rawClassName];
+ if (preserved != null)
+ return preserved;
+ return rawClassName;
+ },
+ _Universe_findRule(universe, targetType) {
+ var rule = universe.tR[targetType];
+ for (; typeof rule == "string";)
+ rule = universe.tR[rule];
+ return rule;
+ },
+ _Universe_findErasedType(universe, cls) {
+ var $length, erased, $arguments, i, $interface,
+ t1 = universe.eT,
+ probe = t1[cls];
+ if (probe == null)
+ return A._Universe_eval(universe, cls, false);
+ else if (typeof probe == "number") {
+ $length = probe;
+ erased = A._Universe__lookupTerminalRti(universe, 5, "#");
+ $arguments = A._Utils_newArrayOrEmpty($length);
+ for (i = 0; i < $length; ++i)
+ $arguments[i] = erased;
+ $interface = A._Universe__lookupInterfaceRti(universe, cls, $arguments);
+ t1[cls] = $interface;
+ return $interface;
+ } else
+ return probe;
+ },
+ _Universe_addRules(universe, rules) {
+ return A._Utils_objectAssign(universe.tR, rules);
+ },
+ _Universe_addErasedTypes(universe, types) {
+ return A._Utils_objectAssign(universe.eT, types);
+ },
+ _Universe_eval(universe, recipe, normalize) {
+ var rti,
+ t1 = universe.eC,
+ probe = t1.get(recipe);
+ if (probe != null)
+ return probe;
+ rti = A._Parser_parse(A._Parser_create(universe, null, recipe, normalize));
+ t1.set(recipe, rti);
+ return rti;
+ },
+ _Universe_evalInEnvironment(universe, environment, recipe) {
+ var probe, rti,
+ cache = environment._evalCache;
+ if (cache == null)
+ cache = environment._evalCache = new Map();
+ probe = cache.get(recipe);
+ if (probe != null)
+ return probe;
+ rti = A._Parser_parse(A._Parser_create(universe, environment, recipe, true));
+ cache.set(recipe, rti);
+ return rti;
+ },
+ _Universe_bind(universe, environment, argumentsRti) {
+ var argumentsRecipe, probe, rti,
+ cache = environment._bindCache;
+ if (cache == null)
+ cache = environment._bindCache = new Map();
+ argumentsRecipe = argumentsRti._canonicalRecipe;
+ probe = cache.get(argumentsRecipe);
+ if (probe != null)
+ return probe;
+ rti = A._Universe__lookupBindingRti(universe, environment, argumentsRti._kind === 10 ? argumentsRti._rest : [argumentsRti]);
+ cache.set(argumentsRecipe, rti);
+ return rti;
+ },
+ _Universe__installTypeTests(universe, rti) {
+ rti._as = A._installSpecializedAsCheck;
+ rti._is = A._installSpecializedIsTest;
+ return rti;
+ },
+ _Universe__lookupTerminalRti(universe, kind, key) {
+ var rti, t1,
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = kind;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupStarRti(universe, baseType, normalize) {
+ var t1,
+ key = baseType._canonicalRecipe + "*",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createStarRti(universe, baseType, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createStarRti(universe, baseType, key, normalize) {
+ var baseKind, t1, rti;
+ if (normalize) {
+ baseKind = baseType._kind;
+ if (!A.isStrongTopType(baseType))
+ t1 = baseType === type$.Null || baseType === type$.JSNull || baseKind === 7 || baseKind === 6;
+ else
+ t1 = true;
+ if (t1)
+ return baseType;
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 6;
+ rti._primary = baseType;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Universe__lookupQuestionRti(universe, baseType, normalize) {
+ var t1,
+ key = baseType._canonicalRecipe + "?",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createQuestionRti(universe, baseType, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createQuestionRti(universe, baseType, key, normalize) {
+ var baseKind, t1, starArgument, rti;
+ if (normalize) {
+ baseKind = baseType._kind;
+ if (!A.isStrongTopType(baseType))
+ if (!(baseType === type$.Null || baseType === type$.JSNull))
+ if (baseKind !== 7)
+ t1 = baseKind === 8 && A.isNullable(baseType._primary);
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ if (t1)
+ return baseType;
+ else if (baseKind === 1 || baseType === type$.legacy_Never)
+ return type$.Null;
+ else if (baseKind === 6) {
+ starArgument = baseType._primary;
+ if (starArgument._kind === 8 && A.isNullable(starArgument._primary))
+ return starArgument;
+ else
+ return A.Rti__getQuestionFromStar(universe, baseType);
+ }
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 7;
+ rti._primary = baseType;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Universe__lookupFutureOrRti(universe, baseType, normalize) {
+ var t1,
+ key = baseType._canonicalRecipe + "/",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createFutureOrRti(universe, baseType, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createFutureOrRti(universe, baseType, key, normalize) {
+ var t1, t2, rti;
+ if (normalize) {
+ t1 = baseType._kind;
+ if (!A.isStrongTopType(baseType))
+ if (!(baseType === type$.legacy_Object))
+ t2 = false;
+ else
+ t2 = true;
+ else
+ t2 = true;
+ if (t2 || baseType === type$.Object)
+ return baseType;
+ else if (t1 === 1)
+ return A._Universe__lookupInterfaceRti(universe, "Future", [baseType]);
+ else if (baseType === type$.Null || baseType === type$.JSNull)
+ return type$.nullable_Future_Null;
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 8;
+ rti._primary = baseType;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Universe__lookupGenericFunctionParameterRti(universe, index) {
+ var rti, t1,
+ key = "" + index + "^",
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 14;
+ rti._primary = index;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__canonicalRecipeJoin($arguments) {
+ var s, sep, i,
+ $length = $arguments.length;
+ for (s = "", sep = "", i = 0; i < $length; ++i, sep = ",")
+ s += sep + $arguments[i]._canonicalRecipe;
+ return s;
+ },
+ _Universe__canonicalRecipeJoinNamed($arguments) {
+ var s, sep, i, t1, nameSep,
+ $length = $arguments.length;
+ for (s = "", sep = "", i = 0; i < $length; i += 3, sep = ",") {
+ t1 = $arguments[i];
+ nameSep = $arguments[i + 1] ? "!" : ":";
+ s += sep + t1 + nameSep + $arguments[i + 2]._canonicalRecipe;
+ }
+ return s;
+ },
+ _Universe__lookupInterfaceRti(universe, $name, $arguments) {
+ var probe, rti, t1,
+ s = $name;
+ if ($arguments.length > 0)
+ s += "<" + A._Universe__canonicalRecipeJoin($arguments) + ">";
+ probe = universe.eC.get(s);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 9;
+ rti._primary = $name;
+ rti._rest = $arguments;
+ if ($arguments.length > 0)
+ rti._precomputed1 = $arguments[0];
+ rti._canonicalRecipe = s;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(s, t1);
+ return t1;
+ },
+ _Universe__lookupBindingRti(universe, base, $arguments) {
+ var newBase, newArguments, key, probe, rti, t1;
+ if (base._kind === 10) {
+ newBase = base._primary;
+ newArguments = base._rest.concat($arguments);
+ } else {
+ newArguments = $arguments;
+ newBase = base;
+ }
+ key = newBase._canonicalRecipe + (";<" + A._Universe__canonicalRecipeJoin(newArguments) + ">");
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 10;
+ rti._primary = newBase;
+ rti._rest = newArguments;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupRecordRti(universe, partialShapeTag, fields) {
+ var rti, t1,
+ key = "+" + (partialShapeTag + "(" + A._Universe__canonicalRecipeJoin(fields) + ")"),
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 11;
+ rti._primary = partialShapeTag;
+ rti._rest = fields;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupFunctionRti(universe, returnType, parameters) {
+ var sep, key, probe, rti, t1,
+ s = returnType._canonicalRecipe,
+ requiredPositional = parameters._requiredPositional,
+ requiredPositionalLength = requiredPositional.length,
+ optionalPositional = parameters._optionalPositional,
+ optionalPositionalLength = optionalPositional.length,
+ named = parameters._named,
+ namedLength = named.length,
+ recipe = "(" + A._Universe__canonicalRecipeJoin(requiredPositional);
+ if (optionalPositionalLength > 0) {
+ sep = requiredPositionalLength > 0 ? "," : "";
+ recipe += sep + "[" + A._Universe__canonicalRecipeJoin(optionalPositional) + "]";
+ }
+ if (namedLength > 0) {
+ sep = requiredPositionalLength > 0 ? "," : "";
+ recipe += sep + "{" + A._Universe__canonicalRecipeJoinNamed(named) + "}";
+ }
+ key = s + (recipe + ")");
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ rti = new A.Rti(null, null);
+ rti._kind = 12;
+ rti._primary = returnType;
+ rti._rest = parameters;
+ rti._canonicalRecipe = key;
+ t1 = A._Universe__installTypeTests(universe, rti);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__lookupGenericFunctionRti(universe, baseFunctionType, bounds, normalize) {
+ var t1,
+ key = baseFunctionType._canonicalRecipe + ("<" + A._Universe__canonicalRecipeJoin(bounds) + ">"),
+ probe = universe.eC.get(key);
+ if (probe != null)
+ return probe;
+ t1 = A._Universe__createGenericFunctionRti(universe, baseFunctionType, bounds, key, normalize);
+ universe.eC.set(key, t1);
+ return t1;
+ },
+ _Universe__createGenericFunctionRti(universe, baseFunctionType, bounds, key, normalize) {
+ var $length, typeArguments, count, i, bound, substitutedBase, substitutedBounds, rti;
+ if (normalize) {
+ $length = bounds.length;
+ typeArguments = A._Utils_newArrayOrEmpty($length);
+ for (count = 0, i = 0; i < $length; ++i) {
+ bound = bounds[i];
+ if (bound._kind === 1) {
+ typeArguments[i] = bound;
+ ++count;
+ }
+ }
+ if (count > 0) {
+ substitutedBase = A._substitute(universe, baseFunctionType, typeArguments, 0);
+ substitutedBounds = A._substituteArray(universe, bounds, typeArguments, 0);
+ return A._Universe__lookupGenericFunctionRti(universe, substitutedBase, substitutedBounds, bounds !== substitutedBounds);
+ }
+ }
+ rti = new A.Rti(null, null);
+ rti._kind = 13;
+ rti._primary = baseFunctionType;
+ rti._rest = bounds;
+ rti._canonicalRecipe = key;
+ return A._Universe__installTypeTests(universe, rti);
+ },
+ _Parser_create(universe, environment, recipe, normalize) {
+ return {u: universe, e: environment, r: recipe, s: [], p: 0, n: normalize};
+ },
+ _Parser_parse(parser) {
+ var t2, i, ch, t3, array, end, item,
+ source = parser.r,
+ t1 = parser.s;
+ for (t2 = source.length, i = 0; i < t2;) {
+ ch = source.charCodeAt(i);
+ if (ch >= 48 && ch <= 57)
+ i = A._Parser_handleDigit(i + 1, ch, source, t1);
+ else if ((((ch | 32) >>> 0) - 97 & 65535) < 26 || ch === 95 || ch === 36 || ch === 124)
+ i = A._Parser_handleIdentifier(parser, i, source, t1, false);
+ else if (ch === 46)
+ i = A._Parser_handleIdentifier(parser, i, source, t1, true);
+ else {
+ ++i;
+ switch (ch) {
+ case 44:
+ break;
+ case 58:
+ t1.push(false);
+ break;
+ case 33:
+ t1.push(true);
+ break;
+ case 59:
+ t1.push(A._Parser_toType(parser.u, parser.e, t1.pop()));
+ break;
+ case 94:
+ t1.push(A._Universe__lookupGenericFunctionParameterRti(parser.u, t1.pop()));
+ break;
+ case 35:
+ t1.push(A._Universe__lookupTerminalRti(parser.u, 5, "#"));
+ break;
+ case 64:
+ t1.push(A._Universe__lookupTerminalRti(parser.u, 2, "@"));
+ break;
+ case 126:
+ t1.push(A._Universe__lookupTerminalRti(parser.u, 3, "~"));
+ break;
+ case 60:
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 62:
+ A._Parser_handleTypeArguments(parser, t1);
+ break;
+ case 38:
+ A._Parser_handleExtendedOperations(parser, t1);
+ break;
+ case 42:
+ t3 = parser.u;
+ t1.push(A._Universe__lookupStarRti(t3, A._Parser_toType(t3, parser.e, t1.pop()), parser.n));
+ break;
+ case 63:
+ t3 = parser.u;
+ t1.push(A._Universe__lookupQuestionRti(t3, A._Parser_toType(t3, parser.e, t1.pop()), parser.n));
+ break;
+ case 47:
+ t3 = parser.u;
+ t1.push(A._Universe__lookupFutureOrRti(t3, A._Parser_toType(t3, parser.e, t1.pop()), parser.n));
+ break;
+ case 40:
+ t1.push(-3);
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 41:
+ A._Parser_handleArguments(parser, t1);
+ break;
+ case 91:
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 93:
+ array = t1.splice(parser.p);
+ A._Parser_toTypes(parser.u, parser.e, array);
+ parser.p = t1.pop();
+ t1.push(array);
+ t1.push(-1);
+ break;
+ case 123:
+ t1.push(parser.p);
+ parser.p = t1.length;
+ break;
+ case 125:
+ array = t1.splice(parser.p);
+ A._Parser_toTypesNamed(parser.u, parser.e, array);
+ parser.p = t1.pop();
+ t1.push(array);
+ t1.push(-2);
+ break;
+ case 43:
+ end = source.indexOf("(", i);
+ t1.push(source.substring(i, end));
+ t1.push(-4);
+ t1.push(parser.p);
+ parser.p = t1.length;
+ i = end + 1;
+ break;
+ default:
+ throw "Bad character " + ch;
+ }
+ }
+ }
+ item = t1.pop();
+ return A._Parser_toType(parser.u, parser.e, item);
+ },
+ _Parser_handleDigit(i, digit, source, stack) {
+ var t1, ch,
+ value = digit - 48;
+ for (t1 = source.length; i < t1; ++i) {
+ ch = source.charCodeAt(i);
+ if (!(ch >= 48 && ch <= 57))
+ break;
+ value = value * 10 + (ch - 48);
+ }
+ stack.push(value);
+ return i;
+ },
+ _Parser_handleIdentifier(parser, start, source, stack, hasPeriod) {
+ var t1, ch, t2, string, environment, recipe,
+ i = start + 1;
+ for (t1 = source.length; i < t1; ++i) {
+ ch = source.charCodeAt(i);
+ if (ch === 46) {
+ if (hasPeriod)
+ break;
+ hasPeriod = true;
+ } else {
+ if (!((((ch | 32) >>> 0) - 97 & 65535) < 26 || ch === 95 || ch === 36 || ch === 124))
+ t2 = ch >= 48 && ch <= 57;
+ else
+ t2 = true;
+ if (!t2)
+ break;
+ }
+ }
+ string = source.substring(start, i);
+ if (hasPeriod) {
+ t1 = parser.u;
+ environment = parser.e;
+ if (environment._kind === 10)
+ environment = environment._primary;
+ recipe = A._Universe_findRule(t1, environment._primary)[string];
+ if (recipe == null)
+ A.throwExpression('No "' + string + '" in "' + A.Rti__getCanonicalRecipe(environment) + '"');
+ stack.push(A._Universe_evalInEnvironment(t1, environment, recipe));
+ } else
+ stack.push(string);
+ return i;
+ },
+ _Parser_handleTypeArguments(parser, stack) {
+ var base,
+ t1 = parser.u,
+ $arguments = A._Parser_collectArray(parser, stack),
+ head = stack.pop();
+ if (typeof head == "string")
+ stack.push(A._Universe__lookupInterfaceRti(t1, head, $arguments));
+ else {
+ base = A._Parser_toType(t1, parser.e, head);
+ switch (base._kind) {
+ case 12:
+ stack.push(A._Universe__lookupGenericFunctionRti(t1, base, $arguments, parser.n));
+ break;
+ default:
+ stack.push(A._Universe__lookupBindingRti(t1, base, $arguments));
+ break;
+ }
+ }
+ },
+ _Parser_handleArguments(parser, stack) {
+ var optionalPositional, named, requiredPositional, returnType, parameters, _null = null,
+ t1 = parser.u,
+ head = stack.pop();
+ if (typeof head == "number")
+ switch (head) {
+ case -1:
+ optionalPositional = stack.pop();
+ named = _null;
+ break;
+ case -2:
+ named = stack.pop();
+ optionalPositional = _null;
+ break;
+ default:
+ stack.push(head);
+ named = _null;
+ optionalPositional = named;
+ break;
+ }
+ else {
+ stack.push(head);
+ named = _null;
+ optionalPositional = named;
+ }
+ requiredPositional = A._Parser_collectArray(parser, stack);
+ head = stack.pop();
+ switch (head) {
+ case -3:
+ head = stack.pop();
+ if (optionalPositional == null)
+ optionalPositional = t1.sEA;
+ if (named == null)
+ named = t1.sEA;
+ returnType = A._Parser_toType(t1, parser.e, head);
+ parameters = new A._FunctionParameters();
+ parameters._requiredPositional = requiredPositional;
+ parameters._optionalPositional = optionalPositional;
+ parameters._named = named;
+ stack.push(A._Universe__lookupFunctionRti(t1, returnType, parameters));
+ return;
+ case -4:
+ stack.push(A._Universe__lookupRecordRti(t1, stack.pop(), requiredPositional));
+ return;
+ default:
+ throw A.wrapException(A.AssertionError$("Unexpected state under `()`: " + A.S(head)));
+ }
+ },
+ _Parser_handleExtendedOperations(parser, stack) {
+ var $top = stack.pop();
+ if (0 === $top) {
+ stack.push(A._Universe__lookupTerminalRti(parser.u, 1, "0&"));
+ return;
+ }
+ if (1 === $top) {
+ stack.push(A._Universe__lookupTerminalRti(parser.u, 4, "1&"));
+ return;
+ }
+ throw A.wrapException(A.AssertionError$("Unexpected extended operation " + A.S($top)));
+ },
+ _Parser_collectArray(parser, stack) {
+ var array = stack.splice(parser.p);
+ A._Parser_toTypes(parser.u, parser.e, array);
+ parser.p = stack.pop();
+ return array;
+ },
+ _Parser_toType(universe, environment, item) {
+ if (typeof item == "string")
+ return A._Universe__lookupInterfaceRti(universe, item, universe.sEA);
+ else if (typeof item == "number") {
+ environment.toString;
+ return A._Parser_indexToType(universe, environment, item);
+ } else
+ return item;
+ },
+ _Parser_toTypes(universe, environment, items) {
+ var i,
+ $length = items.length;
+ for (i = 0; i < $length; ++i)
+ items[i] = A._Parser_toType(universe, environment, items[i]);
+ },
+ _Parser_toTypesNamed(universe, environment, items) {
+ var i,
+ $length = items.length;
+ for (i = 2; i < $length; i += 3)
+ items[i] = A._Parser_toType(universe, environment, items[i]);
+ },
+ _Parser_indexToType(universe, environment, index) {
+ var typeArguments, len,
+ kind = environment._kind;
+ if (kind === 10) {
+ if (index === 0)
+ return environment._primary;
+ typeArguments = environment._rest;
+ len = typeArguments.length;
+ if (index <= len)
+ return typeArguments[index - 1];
+ index -= len;
+ environment = environment._primary;
+ kind = environment._kind;
+ } else if (index === 0)
+ return environment;
+ if (kind !== 9)
+ throw A.wrapException(A.AssertionError$("Indexed base must be an interface type"));
+ typeArguments = environment._rest;
+ if (index <= typeArguments.length)
+ return typeArguments[index - 1];
+ throw A.wrapException(A.AssertionError$("Bad index " + index + " for " + environment.toString$0(0)));
+ },
+ isSubtype(universe, s, t) {
+ var result,
+ sCache = A.Rti__getIsSubtypeCache(s),
+ probe = sCache.get(t);
+ if (probe != null)
+ return probe;
+ result = A._isSubtype(universe, s, null, t, null);
+ sCache.set(t, result);
+ return result;
+ },
+ _isSubtype(universe, s, sEnv, t, tEnv) {
+ var t1, sKind, leftTypeVariable, tKind, t2, sBounds, tBounds, sLength, i, sBound, tBound;
+ if (s === t)
+ return true;
+ if (!A.isStrongTopType(t))
+ if (!(t === type$.legacy_Object))
+ t1 = false;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ if (t1)
+ return true;
+ sKind = s._kind;
+ if (sKind === 4)
+ return true;
+ if (A.isStrongTopType(s))
+ return false;
+ if (s._kind !== 1)
+ t1 = false;
+ else
+ t1 = true;
+ if (t1)
+ return true;
+ leftTypeVariable = sKind === 14;
+ if (leftTypeVariable)
+ if (A._isSubtype(universe, sEnv[s._primary], sEnv, t, tEnv))
+ return true;
+ tKind = t._kind;
+ t1 = s === type$.Null || s === type$.JSNull;
+ if (t1) {
+ if (tKind === 8)
+ return A._isSubtype(universe, s, sEnv, t._primary, tEnv);
+ return t === type$.Null || t === type$.JSNull || tKind === 7 || tKind === 6;
+ }
+ if (t === type$.Object) {
+ if (sKind === 8)
+ return A._isSubtype(universe, s._primary, sEnv, t, tEnv);
+ if (sKind === 6)
+ return A._isSubtype(universe, s._primary, sEnv, t, tEnv);
+ return sKind !== 7;
+ }
+ if (sKind === 6)
+ return A._isSubtype(universe, s._primary, sEnv, t, tEnv);
+ if (tKind === 6) {
+ t1 = A.Rti__getQuestionFromStar(universe, t);
+ return A._isSubtype(universe, s, sEnv, t1, tEnv);
+ }
+ if (sKind === 8) {
+ if (!A._isSubtype(universe, s._primary, sEnv, t, tEnv))
+ return false;
+ return A._isSubtype(universe, A.Rti__getFutureFromFutureOr(universe, s), sEnv, t, tEnv);
+ }
+ if (sKind === 7) {
+ t1 = A._isSubtype(universe, type$.Null, sEnv, t, tEnv);
+ return t1 && A._isSubtype(universe, s._primary, sEnv, t, tEnv);
+ }
+ if (tKind === 8) {
+ if (A._isSubtype(universe, s, sEnv, t._primary, tEnv))
+ return true;
+ return A._isSubtype(universe, s, sEnv, A.Rti__getFutureFromFutureOr(universe, t), tEnv);
+ }
+ if (tKind === 7) {
+ t1 = A._isSubtype(universe, s, sEnv, type$.Null, tEnv);
+ return t1 || A._isSubtype(universe, s, sEnv, t._primary, tEnv);
+ }
+ if (leftTypeVariable)
+ return false;
+ t1 = sKind !== 12;
+ if ((!t1 || sKind === 13) && t === type$.Function)
+ return true;
+ t2 = sKind === 11;
+ if (t2 && t === type$.Record)
+ return true;
+ if (tKind === 13) {
+ if (s === type$.JavaScriptFunction)
+ return true;
+ if (sKind !== 13)
+ return false;
+ sBounds = s._rest;
+ tBounds = t._rest;
+ sLength = sBounds.length;
+ if (sLength !== tBounds.length)
+ return false;
+ sEnv = sEnv == null ? sBounds : sBounds.concat(sEnv);
+ tEnv = tEnv == null ? tBounds : tBounds.concat(tEnv);
+ for (i = 0; i < sLength; ++i) {
+ sBound = sBounds[i];
+ tBound = tBounds[i];
+ if (!A._isSubtype(universe, sBound, sEnv, tBound, tEnv) || !A._isSubtype(universe, tBound, tEnv, sBound, sEnv))
+ return false;
+ }
+ return A._isFunctionSubtype(universe, s._primary, sEnv, t._primary, tEnv);
+ }
+ if (tKind === 12) {
+ if (s === type$.JavaScriptFunction)
+ return true;
+ if (t1)
+ return false;
+ return A._isFunctionSubtype(universe, s, sEnv, t, tEnv);
+ }
+ if (sKind === 9) {
+ if (tKind !== 9)
+ return false;
+ return A._isInterfaceSubtype(universe, s, sEnv, t, tEnv);
+ }
+ if (t2 && tKind === 11)
+ return A._isRecordSubtype(universe, s, sEnv, t, tEnv);
+ return false;
+ },
+ _isFunctionSubtype(universe, s, sEnv, t, tEnv) {
+ var sParameters, tParameters, sRequiredPositional, tRequiredPositional, sRequiredPositionalLength, tRequiredPositionalLength, requiredPositionalDelta, sOptionalPositional, tOptionalPositional, sOptionalPositionalLength, tOptionalPositionalLength, i, t1, sNamed, tNamed, sNamedLength, tNamedLength, sIndex, tIndex, tName, sName, sIsRequired;
+ if (!A._isSubtype(universe, s._primary, sEnv, t._primary, tEnv))
+ return false;
+ sParameters = s._rest;
+ tParameters = t._rest;
+ sRequiredPositional = sParameters._requiredPositional;
+ tRequiredPositional = tParameters._requiredPositional;
+ sRequiredPositionalLength = sRequiredPositional.length;
+ tRequiredPositionalLength = tRequiredPositional.length;
+ if (sRequiredPositionalLength > tRequiredPositionalLength)
+ return false;
+ requiredPositionalDelta = tRequiredPositionalLength - sRequiredPositionalLength;
+ sOptionalPositional = sParameters._optionalPositional;
+ tOptionalPositional = tParameters._optionalPositional;
+ sOptionalPositionalLength = sOptionalPositional.length;
+ tOptionalPositionalLength = tOptionalPositional.length;
+ if (sRequiredPositionalLength + sOptionalPositionalLength < tRequiredPositionalLength + tOptionalPositionalLength)
+ return false;
+ for (i = 0; i < sRequiredPositionalLength; ++i) {
+ t1 = sRequiredPositional[i];
+ if (!A._isSubtype(universe, tRequiredPositional[i], tEnv, t1, sEnv))
+ return false;
+ }
+ for (i = 0; i < requiredPositionalDelta; ++i) {
+ t1 = sOptionalPositional[i];
+ if (!A._isSubtype(universe, tRequiredPositional[sRequiredPositionalLength + i], tEnv, t1, sEnv))
+ return false;
+ }
+ for (i = 0; i < tOptionalPositionalLength; ++i) {
+ t1 = sOptionalPositional[requiredPositionalDelta + i];
+ if (!A._isSubtype(universe, tOptionalPositional[i], tEnv, t1, sEnv))
+ return false;
+ }
+ sNamed = sParameters._named;
+ tNamed = tParameters._named;
+ sNamedLength = sNamed.length;
+ tNamedLength = tNamed.length;
+ for (sIndex = 0, tIndex = 0; tIndex < tNamedLength; tIndex += 3) {
+ tName = tNamed[tIndex];
+ for (; true;) {
+ if (sIndex >= sNamedLength)
+ return false;
+ sName = sNamed[sIndex];
+ sIndex += 3;
+ if (tName < sName)
+ return false;
+ sIsRequired = sNamed[sIndex - 2];
+ if (sName < tName) {
+ if (sIsRequired)
+ return false;
+ continue;
+ }
+ t1 = tNamed[tIndex + 1];
+ if (sIsRequired && !t1)
+ return false;
+ t1 = sNamed[sIndex - 1];
+ if (!A._isSubtype(universe, tNamed[tIndex + 2], tEnv, t1, sEnv))
+ return false;
+ break;
+ }
+ }
+ for (; sIndex < sNamedLength;) {
+ if (sNamed[sIndex + 1])
+ return false;
+ sIndex += 3;
+ }
+ return true;
+ },
+ _isInterfaceSubtype(universe, s, sEnv, t, tEnv) {
+ var rule, recipes, $length, supertypeArgs, i, t1, t2,
+ sName = s._primary,
+ tName = t._primary;
+ for (; sName !== tName;) {
+ rule = universe.tR[sName];
+ if (rule == null)
+ return false;
+ if (typeof rule == "string") {
+ sName = rule;
+ continue;
+ }
+ recipes = rule[tName];
+ if (recipes == null)
+ return false;
+ $length = recipes.length;
+ supertypeArgs = $length > 0 ? new Array($length) : init.typeUniverse.sEA;
+ for (i = 0; i < $length; ++i)
+ supertypeArgs[i] = A._Universe_evalInEnvironment(universe, s, recipes[i]);
+ return A._areArgumentsSubtypes(universe, supertypeArgs, null, sEnv, t._rest, tEnv);
+ }
+ t1 = s._rest;
+ t2 = t._rest;
+ return A._areArgumentsSubtypes(universe, t1, null, sEnv, t2, tEnv);
+ },
+ _areArgumentsSubtypes(universe, sArgs, sVariances, sEnv, tArgs, tEnv) {
+ var i, t1, t2,
+ $length = sArgs.length;
+ for (i = 0; i < $length; ++i) {
+ t1 = sArgs[i];
+ t2 = tArgs[i];
+ if (!A._isSubtype(universe, t1, sEnv, t2, tEnv))
+ return false;
+ }
+ return true;
+ },
+ _isRecordSubtype(universe, s, sEnv, t, tEnv) {
+ var i,
+ sFields = s._rest,
+ tFields = t._rest,
+ sCount = sFields.length;
+ if (sCount !== tFields.length)
+ return false;
+ if (s._primary !== t._primary)
+ return false;
+ for (i = 0; i < sCount; ++i)
+ if (!A._isSubtype(universe, sFields[i], sEnv, tFields[i], tEnv))
+ return false;
+ return true;
+ },
+ isNullable(t) {
+ var t1,
+ kind = t._kind;
+ if (!(t === type$.Null || t === type$.JSNull))
+ if (!A.isStrongTopType(t))
+ if (kind !== 7)
+ if (!(kind === 6 && A.isNullable(t._primary)))
+ t1 = kind === 8 && A.isNullable(t._primary);
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ return t1;
+ },
+ isTopType(t) {
+ var t1;
+ if (!A.isStrongTopType(t))
+ if (!(t === type$.legacy_Object))
+ t1 = false;
+ else
+ t1 = true;
+ else
+ t1 = true;
+ return t1;
+ },
+ isStrongTopType(t) {
+ var kind = t._kind;
+ return kind === 2 || kind === 3 || kind === 4 || kind === 5 || t === type$.nullable_Object;
+ },
+ _Utils_objectAssign(o, other) {
+ var i, key,
+ keys = Object.keys(other),
+ $length = keys.length;
+ for (i = 0; i < $length; ++i) {
+ key = keys[i];
+ o[key] = other[key];
+ }
+ },
+ _Utils_newArrayOrEmpty($length) {
+ return $length > 0 ? new Array($length) : init.typeUniverse.sEA;
+ },
+ Rti: function Rti(t0, t1) {
+ var _ = this;
+ _._as = t0;
+ _._is = t1;
+ _._cachedRuntimeType = _._specializedTestResource = _._unsoundIsSubtypeCache = _._isSubtypeCache = _._precomputed1 = null;
+ _._kind = 0;
+ _._canonicalRecipe = _._bindCache = _._evalCache = _._rest = _._primary = null;
+ },
+ _FunctionParameters: function _FunctionParameters() {
+ this._named = this._optionalPositional = this._requiredPositional = null;
+ },
+ _Type: function _Type(t0) {
+ this._rti = t0;
+ },
+ _Error: function _Error() {
+ },
+ _TypeError: function _TypeError(t0) {
+ this.__rti$_message = t0;
+ },
+ _AsyncRun__initializeScheduleImmediate() {
+ var div, span, t1 = {};
+ if (self.scheduleImmediate != null)
+ return A.async__AsyncRun__scheduleImmediateJsOverride$closure();
+ if (self.MutationObserver != null && self.document != null) {
+ div = self.document.createElement("div");
+ span = self.document.createElement("span");
+ t1.storedCallback = null;
+ new self.MutationObserver(A.convertDartClosureToJS(new A._AsyncRun__initializeScheduleImmediate_internalCallback(t1), 1)).observe(div, {childList: true});
+ return new A._AsyncRun__initializeScheduleImmediate_closure(t1, div, span);
+ } else if (self.setImmediate != null)
+ return A.async__AsyncRun__scheduleImmediateWithSetImmediate$closure();
+ return A.async__AsyncRun__scheduleImmediateWithTimer$closure();
+ },
+ _AsyncRun__scheduleImmediateJsOverride(callback) {
+ self.scheduleImmediate(A.convertDartClosureToJS(new A._AsyncRun__scheduleImmediateJsOverride_internalCallback(type$.void_Function._as(callback)), 0));
+ },
+ _AsyncRun__scheduleImmediateWithSetImmediate(callback) {
+ self.setImmediate(A.convertDartClosureToJS(new A._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback(type$.void_Function._as(callback)), 0));
+ },
+ _AsyncRun__scheduleImmediateWithTimer(callback) {
+ A.Timer__createTimer(B.Duration_0, type$.void_Function._as(callback));
+ },
+ Timer__createTimer(duration, callback) {
+ var milliseconds = B.JSInt_methods._tdivFast$1(duration._duration, 1000);
+ return A._TimerImpl$(milliseconds, callback);
+ },
+ Timer__createPeriodicTimer(duration, callback) {
+ var milliseconds = B.JSInt_methods._tdivFast$1(duration._duration, 1000);
+ return A._TimerImpl$periodic(milliseconds, callback);
+ },
+ _TimerImpl$(milliseconds, callback) {
+ var t1 = new A._TimerImpl();
+ t1._TimerImpl$2(milliseconds, callback);
+ return t1;
+ },
+ _TimerImpl$periodic(milliseconds, callback) {
+ var t1 = new A._TimerImpl();
+ t1._TimerImpl$periodic$2(milliseconds, callback);
+ return t1;
+ },
+ AsyncError$(error, stackTrace) {
+ var t1 = A.checkNotNullable(error, "error", type$.Object);
+ return new A.AsyncError(t1, stackTrace == null ? A.AsyncError_defaultStackTrace(error) : stackTrace);
+ },
+ AsyncError_defaultStackTrace(error) {
+ var stackTrace;
+ if (type$.Error._is(error)) {
+ stackTrace = error.get$stackTrace();
+ if (stackTrace != null)
+ return stackTrace;
+ }
+ return B._StringStackTrace_3uE;
+ },
+ Future_Future$value(value, $T) {
+ var t1;
+ $T._as(value);
+ t1 = new A._Future($.Zone__current, $T._eval$1("_Future<0>"));
+ t1._asyncComplete$1(value);
+ return t1;
+ },
+ _Future__chainCoreFutureSync(source, target) {
+ var t1, t2, listeners;
+ for (t1 = type$._Future_dynamic; t2 = source._async$_state, (t2 & 4) !== 0;)
+ source = t1._as(source._resultOrListeners);
+ if ((t2 & 24) !== 0) {
+ listeners = target._removeListeners$0();
+ target._cloneResult$1(source);
+ A._Future__propagateToListeners(target, listeners);
+ } else {
+ listeners = type$.nullable__FutureListener_dynamic_dynamic._as(target._resultOrListeners);
+ target._setChained$1(source);
+ source._prependListeners$1(listeners);
+ }
+ },
+ _Future__chainCoreFutureAsync(source, target) {
+ var t2, t3, listeners, _box_0 = {},
+ t1 = _box_0.source = source;
+ for (t2 = type$._Future_dynamic; t3 = t1._async$_state, (t3 & 4) !== 0; t1 = source) {
+ source = t2._as(t1._resultOrListeners);
+ _box_0.source = source;
+ }
+ if ((t3 & 24) === 0) {
+ listeners = type$.nullable__FutureListener_dynamic_dynamic._as(target._resultOrListeners);
+ target._setChained$1(t1);
+ _box_0.source._prependListeners$1(listeners);
+ return;
+ }
+ if ((t3 & 16) === 0 && target._resultOrListeners == null) {
+ target._cloneResult$1(t1);
+ return;
+ }
+ target._async$_state ^= 2;
+ target._zone.scheduleMicrotask$1(new A._Future__chainCoreFutureAsync_closure(_box_0, target));
+ },
+ _Future__propagateToListeners(source, listeners) {
+ var t2, t3, t4, _box_0, t5, t6, hasError, asyncError, nextListener, nextListener0, sourceResult, t7, zone, oldZone, result, current, _box_1 = {},
+ t1 = _box_1.source = source;
+ for (t2 = type$.AsyncError, t3 = type$.nullable__FutureListener_dynamic_dynamic, t4 = type$.Future_dynamic; true;) {
+ _box_0 = {};
+ t5 = t1._async$_state;
+ t6 = (t5 & 16) === 0;
+ hasError = !t6;
+ if (listeners == null) {
+ if (hasError && (t5 & 1) === 0) {
+ asyncError = t2._as(t1._resultOrListeners);
+ t1._zone.handleUncaughtError$2(asyncError.error, asyncError.stackTrace);
+ }
+ return;
+ }
+ _box_0.listener = listeners;
+ nextListener = listeners._nextListener;
+ for (t1 = listeners; nextListener != null; t1 = nextListener, nextListener = nextListener0) {
+ t1._nextListener = null;
+ A._Future__propagateToListeners(_box_1.source, t1);
+ _box_0.listener = nextListener;
+ nextListener0 = nextListener._nextListener;
+ }
+ t5 = _box_1.source;
+ sourceResult = t5._resultOrListeners;
+ _box_0.listenerHasError = hasError;
+ _box_0.listenerValueOrError = sourceResult;
+ if (t6) {
+ t7 = t1.state;
+ t7 = (t7 & 1) !== 0 || (t7 & 15) === 8;
+ } else
+ t7 = true;
+ if (t7) {
+ zone = t1.result._zone;
+ if (hasError) {
+ t1 = t5._zone;
+ t1 = !(t1 === zone || t1.get$errorZone() === zone.get$errorZone());
+ } else
+ t1 = false;
+ if (t1) {
+ t1 = _box_1.source;
+ asyncError = t2._as(t1._resultOrListeners);
+ t1._zone.handleUncaughtError$2(asyncError.error, asyncError.stackTrace);
+ return;
+ }
+ oldZone = $.Zone__current;
+ if (oldZone !== zone)
+ $.Zone__current = zone;
+ else
+ oldZone = null;
+ t1 = _box_0.listener.state;
+ if ((t1 & 15) === 8)
+ new A._Future__propagateToListeners_handleWhenCompleteCallback(_box_0, _box_1, hasError).call$0();
+ else if (t6) {
+ if ((t1 & 1) !== 0)
+ new A._Future__propagateToListeners_handleValueCallback(_box_0, sourceResult).call$0();
+ } else if ((t1 & 2) !== 0)
+ new A._Future__propagateToListeners_handleError(_box_1, _box_0).call$0();
+ if (oldZone != null)
+ $.Zone__current = oldZone;
+ t1 = _box_0.listenerValueOrError;
+ if (t1 instanceof A._Future) {
+ t5 = _box_0.listener.$ti;
+ t5 = t5._eval$1("Future<2>")._is(t1) || !t5._rest[1]._is(t1);
+ } else
+ t5 = false;
+ if (t5) {
+ t4._as(t1);
+ result = _box_0.listener.result;
+ if ((t1._async$_state & 24) !== 0) {
+ current = t3._as(result._resultOrListeners);
+ result._resultOrListeners = null;
+ listeners = result._reverseListeners$1(current);
+ result._async$_state = t1._async$_state & 30 | result._async$_state & 1;
+ result._resultOrListeners = t1._resultOrListeners;
+ _box_1.source = t1;
+ continue;
+ } else
+ A._Future__chainCoreFutureSync(t1, result);
+ return;
+ }
+ }
+ result = _box_0.listener.result;
+ current = t3._as(result._resultOrListeners);
+ result._resultOrListeners = null;
+ listeners = result._reverseListeners$1(current);
+ t1 = _box_0.listenerHasError;
+ t5 = _box_0.listenerValueOrError;
+ if (!t1) {
+ result.$ti._precomputed1._as(t5);
+ result._async$_state = 8;
+ result._resultOrListeners = t5;
+ } else {
+ t2._as(t5);
+ result._async$_state = result._async$_state & 1 | 16;
+ result._resultOrListeners = t5;
+ }
+ _box_1.source = result;
+ t1 = result;
+ }
+ },
+ _registerErrorHandler(errorHandler, zone) {
+ if (type$.dynamic_Function_Object_StackTrace._is(errorHandler))
+ return zone.registerBinaryCallback$3$1(errorHandler, type$.dynamic, type$.Object, type$.StackTrace);
+ if (type$.dynamic_Function_Object._is(errorHandler))
+ return zone.registerUnaryCallback$2$1(errorHandler, type$.dynamic, type$.Object);
+ throw A.wrapException(A.ArgumentError$value(errorHandler, "onError", string$.Error_));
+ },
+ _microtaskLoop() {
+ var entry, next;
+ for (entry = $._nextCallback; entry != null; entry = $._nextCallback) {
+ $._lastPriorityCallback = null;
+ next = entry.next;
+ $._nextCallback = next;
+ if (next == null)
+ $._lastCallback = null;
+ entry.callback.call$0();
+ }
+ },
+ _startMicrotaskLoop() {
+ $._isInCallbackLoop = true;
+ try {
+ A._microtaskLoop();
+ } finally {
+ $._lastPriorityCallback = null;
+ $._isInCallbackLoop = false;
+ if ($._nextCallback != null)
+ $.$get$_AsyncRun__scheduleImmediateClosure().call$1(A.async___startMicrotaskLoop$closure());
+ }
+ },
+ _scheduleAsyncCallback(callback) {
+ var newEntry = new A._AsyncCallbackEntry(callback),
+ lastCallback = $._lastCallback;
+ if (lastCallback == null) {
+ $._nextCallback = $._lastCallback = newEntry;
+ if (!$._isInCallbackLoop)
+ $.$get$_AsyncRun__scheduleImmediateClosure().call$1(A.async___startMicrotaskLoop$closure());
+ } else
+ $._lastCallback = lastCallback.next = newEntry;
+ },
+ _schedulePriorityAsyncCallback(callback) {
+ var entry, lastPriorityCallback, next,
+ t1 = $._nextCallback;
+ if (t1 == null) {
+ A._scheduleAsyncCallback(callback);
+ $._lastPriorityCallback = $._lastCallback;
+ return;
+ }
+ entry = new A._AsyncCallbackEntry(callback);
+ lastPriorityCallback = $._lastPriorityCallback;
+ if (lastPriorityCallback == null) {
+ entry.next = t1;
+ $._nextCallback = $._lastPriorityCallback = entry;
+ } else {
+ next = lastPriorityCallback.next;
+ entry.next = next;
+ $._lastPriorityCallback = lastPriorityCallback.next = entry;
+ if (next == null)
+ $._lastCallback = entry;
+ }
+ },
+ scheduleMicrotask(callback) {
+ var t1, _null = null,
+ currentZone = $.Zone__current;
+ if (B.C__RootZone === currentZone) {
+ A._rootScheduleMicrotask(_null, _null, B.C__RootZone, callback);
+ return;
+ }
+ if (B.C__RootZone === currentZone.get$_scheduleMicrotask().zone)
+ t1 = B.C__RootZone.get$errorZone() === currentZone.get$errorZone();
+ else
+ t1 = false;
+ if (t1) {
+ A._rootScheduleMicrotask(_null, _null, currentZone, currentZone.registerCallback$1$1(callback, type$.void));
+ return;
+ }
+ t1 = $.Zone__current;
+ t1.scheduleMicrotask$1(t1.bindCallbackGuarded$1(callback));
+ },
+ StreamController_StreamController(onCancel, onListen, sync, $T) {
+ return new A._SyncStreamController(onListen, null, null, onCancel, $T._eval$1("_SyncStreamController<0>"));
+ },
+ _runGuarded(notificationHandler) {
+ var e, s, exception;
+ if (notificationHandler == null)
+ return;
+ try {
+ notificationHandler.call$0();
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ $.Zone__current.handleUncaughtError$2(e, s);
+ }
+ },
+ _BufferingStreamSubscription__registerDataHandler(zone, handleData, $T) {
+ var t1 = handleData == null ? A.async___nullDataHandler$closure() : handleData;
+ return zone.registerUnaryCallback$2$1(t1, type$.void, $T);
+ },
+ _BufferingStreamSubscription__registerErrorHandler(zone, handleError) {
+ if (handleError == null)
+ handleError = A.async___nullErrorHandler$closure();
+ if (type$.void_Function_Object_StackTrace._is(handleError))
+ return zone.registerBinaryCallback$3$1(handleError, type$.dynamic, type$.Object, type$.StackTrace);
+ if (type$.void_Function_Object._is(handleError))
+ return zone.registerUnaryCallback$2$1(handleError, type$.dynamic, type$.Object);
+ throw A.wrapException(A.ArgumentError$(string$.handle, null));
+ },
+ _nullDataHandler(value) {
+ },
+ _nullErrorHandler(error, stackTrace) {
+ type$.Object._as(error);
+ type$.StackTrace._as(stackTrace);
+ $.Zone__current.handleUncaughtError$2(error, stackTrace);
+ },
+ _nullDoneHandler() {
+ },
+ Timer_Timer$periodic(duration, callback) {
+ var boundCallback,
+ t1 = $.Zone__current;
+ if (t1 === B.C__RootZone)
+ return t1.createPeriodicTimer$2(duration, callback);
+ boundCallback = t1.bindUnaryCallbackGuarded$1$1(callback, type$.Timer);
+ return $.Zone__current.createPeriodicTimer$2(duration, boundCallback);
+ },
+ ZoneSpecification_ZoneSpecification$from(other, handleUncaughtError) {
+ var t1 = handleUncaughtError == null ? other.handleUncaughtError : handleUncaughtError;
+ return new A._ZoneSpecification(t1, other.run, other.runUnary, other.runBinary, other.registerCallback, other.registerUnaryCallback, other.registerBinaryCallback, other.errorCallback, other.scheduleMicrotask, other.createTimer, other.createPeriodicTimer, other.print, other.fork);
+ },
+ _rootHandleUncaughtError($self, $parent, zone, error, stackTrace) {
+ A._rootHandleError(error, type$.StackTrace._as(stackTrace));
+ },
+ _rootHandleError(error, stackTrace) {
+ A._schedulePriorityAsyncCallback(new A._rootHandleError_closure(error, stackTrace));
+ },
+ _rootRun($self, $parent, zone, f, $R) {
+ var old, t1;
+ type$.nullable_Zone._as($self);
+ type$.nullable_ZoneDelegate._as($parent);
+ type$.Zone._as(zone);
+ $R._eval$1("0()")._as(f);
+ t1 = $.Zone__current;
+ if (t1 === zone)
+ return f.call$0();
+ $.Zone__current = zone;
+ old = t1;
+ try {
+ t1 = f.call$0();
+ return t1;
+ } finally {
+ $.Zone__current = old;
+ }
+ },
+ _rootRunUnary($self, $parent, zone, f, arg, $R, $T) {
+ var old, t1;
+ type$.nullable_Zone._as($self);
+ type$.nullable_ZoneDelegate._as($parent);
+ type$.Zone._as(zone);
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f);
+ $T._as(arg);
+ t1 = $.Zone__current;
+ if (t1 === zone)
+ return f.call$1(arg);
+ $.Zone__current = zone;
+ old = t1;
+ try {
+ t1 = f.call$1(arg);
+ return t1;
+ } finally {
+ $.Zone__current = old;
+ }
+ },
+ _rootRunBinary($self, $parent, zone, f, arg1, arg2, $R, T1, T2) {
+ var old, t1;
+ type$.nullable_Zone._as($self);
+ type$.nullable_ZoneDelegate._as($parent);
+ type$.Zone._as(zone);
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(f);
+ T1._as(arg1);
+ T2._as(arg2);
+ t1 = $.Zone__current;
+ if (t1 === zone)
+ return f.call$2(arg1, arg2);
+ $.Zone__current = zone;
+ old = t1;
+ try {
+ t1 = f.call$2(arg1, arg2);
+ return t1;
+ } finally {
+ $.Zone__current = old;
+ }
+ },
+ _rootRegisterCallback($self, $parent, zone, f, $R) {
+ return $R._eval$1("0()")._as(f);
+ },
+ _rootRegisterUnaryCallback($self, $parent, zone, f, $R, $T) {
+ return $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f);
+ },
+ _rootRegisterBinaryCallback($self, $parent, zone, f, $R, T1, T2) {
+ return $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(f);
+ },
+ _rootErrorCallback($self, $parent, zone, error, stackTrace) {
+ type$.nullable_StackTrace._as(stackTrace);
+ return null;
+ },
+ _rootScheduleMicrotask($self, $parent, zone, f) {
+ var t1, t2;
+ type$.void_Function._as(f);
+ if (B.C__RootZone !== zone) {
+ t1 = B.C__RootZone.get$errorZone();
+ t2 = zone.get$errorZone();
+ f = t1 !== t2 ? zone.bindCallbackGuarded$1(f) : zone.bindCallback$1$1(f, type$.void);
+ }
+ A._scheduleAsyncCallback(f);
+ },
+ _rootCreateTimer($self, $parent, zone, duration, callback) {
+ type$.Duration._as(duration);
+ type$.void_Function._as(callback);
+ return A.Timer__createTimer(duration, B.C__RootZone !== zone ? zone.bindCallback$1$1(callback, type$.void) : callback);
+ },
+ _rootCreatePeriodicTimer($self, $parent, zone, duration, callback) {
+ type$.Duration._as(duration);
+ type$.void_Function_Timer._as(callback);
+ return A.Timer__createPeriodicTimer(duration, B.C__RootZone !== zone ? zone.bindUnaryCallback$2$1(callback, type$.void, type$.Timer) : callback);
+ },
+ _rootPrint($self, $parent, zone, line) {
+ A.printString(A.S(A._asString(line)));
+ },
+ _rootFork($self, $parent, zone, specification, zoneValues) {
+ var valueMap, t1, handleUncaughtError;
+ type$.nullable_ZoneSpecification._as(specification);
+ type$.nullable_Map_of_nullable_Object_and_nullable_Object._as(zoneValues);
+ valueMap = zone.get$_async$_map();
+ t1 = new A._CustomZone(zone.get$_run(), zone.get$_runUnary(), zone.get$_runBinary(), zone.get$_registerCallback(), zone.get$_registerUnaryCallback(), zone.get$_registerBinaryCallback(), zone.get$_errorCallback(), zone.get$_scheduleMicrotask(), zone.get$_createTimer(), zone.get$_createPeriodicTimer(), zone.get$_print(), zone.get$_fork(), zone.get$_handleUncaughtError(), zone, valueMap);
+ handleUncaughtError = specification.handleUncaughtError;
+ if (handleUncaughtError != null)
+ t1.set$_handleUncaughtError(new A._ZoneFunction(t1, handleUncaughtError, type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace));
+ return t1;
+ },
+ runZonedGuarded(body, onError, $R) {
+ var error, stackTrace, parentZone, errorHandler, t1, exception, _null = null, zoneSpecification = null, zoneValues = null;
+ A.checkNotNullable(body, "body", $R._eval$1("0()"));
+ A.checkNotNullable(onError, "onError", type$.void_Function_Object_StackTrace);
+ parentZone = $.Zone__current;
+ errorHandler = new A.runZonedGuarded_closure(parentZone, onError);
+ if (zoneSpecification == null)
+ zoneSpecification = new A._ZoneSpecification(errorHandler, _null, _null, _null, _null, _null, _null, _null, _null, _null, _null, _null, _null);
+ else
+ zoneSpecification = A.ZoneSpecification_ZoneSpecification$from(zoneSpecification, errorHandler);
+ try {
+ t1 = parentZone.fork$2$specification$zoneValues(zoneSpecification, zoneValues).run$1$1(body, $R);
+ return t1;
+ } catch (exception) {
+ error = A.unwrapException(exception);
+ stackTrace = A.getTraceFromException(exception);
+ onError.call$2(error, stackTrace);
+ }
+ return _null;
+ },
+ _AsyncRun__initializeScheduleImmediate_internalCallback: function _AsyncRun__initializeScheduleImmediate_internalCallback(t0) {
+ this._box_0 = t0;
+ },
+ _AsyncRun__initializeScheduleImmediate_closure: function _AsyncRun__initializeScheduleImmediate_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.div = t1;
+ this.span = t2;
+ },
+ _AsyncRun__scheduleImmediateJsOverride_internalCallback: function _AsyncRun__scheduleImmediateJsOverride_internalCallback(t0) {
+ this.callback = t0;
+ },
+ _AsyncRun__scheduleImmediateWithSetImmediate_internalCallback: function _AsyncRun__scheduleImmediateWithSetImmediate_internalCallback(t0) {
+ this.callback = t0;
+ },
+ _TimerImpl: function _TimerImpl() {
+ this._tick = 0;
+ },
+ _TimerImpl_internalCallback: function _TimerImpl_internalCallback(t0, t1) {
+ this.$this = t0;
+ this.callback = t1;
+ },
+ _TimerImpl$periodic_closure: function _TimerImpl$periodic_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.$this = t0;
+ _.milliseconds = t1;
+ _.start = t2;
+ _.callback = t3;
+ },
+ AsyncError: function AsyncError(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ },
+ _Completer: function _Completer() {
+ },
+ _AsyncCompleter: function _AsyncCompleter(t0, t1) {
+ this.future = t0;
+ this.$ti = t1;
+ },
+ _SyncCompleter: function _SyncCompleter(t0, t1) {
+ this.future = t0;
+ this.$ti = t1;
+ },
+ _FutureListener: function _FutureListener(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._nextListener = null;
+ _.result = t0;
+ _.state = t1;
+ _.callback = t2;
+ _.errorCallback = t3;
+ _.$ti = t4;
+ },
+ _Future: function _Future(t0, t1) {
+ var _ = this;
+ _._async$_state = 0;
+ _._zone = t0;
+ _._resultOrListeners = null;
+ _.$ti = t1;
+ },
+ _Future__addListener_closure: function _Future__addListener_closure(t0, t1) {
+ this.$this = t0;
+ this.listener = t1;
+ },
+ _Future__prependListeners_closure: function _Future__prependListeners_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _Future__chainForeignFuture_closure: function _Future__chainForeignFuture_closure(t0) {
+ this.$this = t0;
+ },
+ _Future__chainForeignFuture_closure0: function _Future__chainForeignFuture_closure0(t0) {
+ this.$this = t0;
+ },
+ _Future__chainForeignFuture_closure1: function _Future__chainForeignFuture_closure1(t0, t1, t2) {
+ this.$this = t0;
+ this.e = t1;
+ this.s = t2;
+ },
+ _Future__chainCoreFutureAsync_closure: function _Future__chainCoreFutureAsync_closure(t0, t1) {
+ this._box_0 = t0;
+ this.target = t1;
+ },
+ _Future__asyncCompleteWithValue_closure: function _Future__asyncCompleteWithValue_closure(t0, t1) {
+ this.$this = t0;
+ this.value = t1;
+ },
+ _Future__asyncCompleteError_closure: function _Future__asyncCompleteError_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _Future__propagateToListeners_handleWhenCompleteCallback: function _Future__propagateToListeners_handleWhenCompleteCallback(t0, t1, t2) {
+ this._box_0 = t0;
+ this._box_1 = t1;
+ this.hasError = t2;
+ },
+ _Future__propagateToListeners_handleWhenCompleteCallback_closure: function _Future__propagateToListeners_handleWhenCompleteCallback_closure(t0) {
+ this.originalSource = t0;
+ },
+ _Future__propagateToListeners_handleValueCallback: function _Future__propagateToListeners_handleValueCallback(t0, t1) {
+ this._box_0 = t0;
+ this.sourceResult = t1;
+ },
+ _Future__propagateToListeners_handleError: function _Future__propagateToListeners_handleError(t0, t1) {
+ this._box_1 = t0;
+ this._box_0 = t1;
+ },
+ _AsyncCallbackEntry: function _AsyncCallbackEntry(t0) {
+ this.callback = t0;
+ this.next = null;
+ },
+ Stream: function Stream() {
+ },
+ Stream_pipe_closure: function Stream_pipe_closure(t0) {
+ this.streamConsumer = t0;
+ },
+ Stream_length_closure: function Stream_length_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ Stream_length_closure0: function Stream_length_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.future = t1;
+ },
+ _StreamController: function _StreamController() {
+ },
+ _StreamController__subscribe_closure: function _StreamController__subscribe_closure(t0) {
+ this.$this = t0;
+ },
+ _StreamController__recordCancel_complete: function _StreamController__recordCancel_complete(t0) {
+ this.$this = t0;
+ },
+ _SyncStreamControllerDispatch: function _SyncStreamControllerDispatch() {
+ },
+ _SyncStreamController: function _SyncStreamController(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._varData = null;
+ _._async$_state = 0;
+ _._doneFuture = null;
+ _.onListen = t0;
+ _.onPause = t1;
+ _.onResume = t2;
+ _.onCancel = t3;
+ _.$ti = t4;
+ },
+ _ControllerStream: function _ControllerStream(t0, t1) {
+ this._controller = t0;
+ this.$ti = t1;
+ },
+ _ControllerSubscription: function _ControllerSubscription(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _._controller = t0;
+ _._onData = t1;
+ _._onError = t2;
+ _._onDone = t3;
+ _._zone = t4;
+ _._async$_state = t5;
+ _._pending = _._cancelFuture = null;
+ _.$ti = t6;
+ },
+ _StreamSinkWrapper: function _StreamSinkWrapper(t0, t1) {
+ this._target = t0;
+ this.$ti = t1;
+ },
+ _AddStreamState_cancel_closure: function _AddStreamState_cancel_closure(t0) {
+ this.$this = t0;
+ },
+ _BufferingStreamSubscription: function _BufferingStreamSubscription() {
+ },
+ _BufferingStreamSubscription__sendError_sendError: function _BufferingStreamSubscription__sendError_sendError(t0, t1, t2) {
+ this.$this = t0;
+ this.error = t1;
+ this.stackTrace = t2;
+ },
+ _BufferingStreamSubscription__sendDone_sendDone: function _BufferingStreamSubscription__sendDone_sendDone(t0) {
+ this.$this = t0;
+ },
+ _StreamImpl: function _StreamImpl() {
+ },
+ _DelayedEvent: function _DelayedEvent() {
+ },
+ _DelayedData: function _DelayedData(t0, t1) {
+ this.value = t0;
+ this.next = null;
+ this.$ti = t1;
+ },
+ _DelayedError: function _DelayedError(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ this.next = null;
+ },
+ _DelayedDone: function _DelayedDone() {
+ },
+ _PendingEvents: function _PendingEvents(t0) {
+ var _ = this;
+ _._async$_state = 0;
+ _.lastPendingEvent = _.firstPendingEvent = null;
+ _.$ti = t0;
+ },
+ _PendingEvents_schedule_closure: function _PendingEvents_schedule_closure(t0, t1) {
+ this.$this = t0;
+ this.dispatch = t1;
+ },
+ _DoneStreamSubscription: function _DoneStreamSubscription(t0, t1) {
+ var _ = this;
+ _._async$_state = 1;
+ _._zone = t0;
+ _._onDone = null;
+ _.$ti = t1;
+ },
+ _EmptyStream: function _EmptyStream(t0) {
+ this.$ti = t0;
+ },
+ _ZoneFunction: function _ZoneFunction(t0, t1, t2) {
+ this.zone = t0;
+ this.$function = t1;
+ this.$ti = t2;
+ },
+ _ZoneSpecification: function _ZoneSpecification(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) {
+ var _ = this;
+ _.handleUncaughtError = t0;
+ _.run = t1;
+ _.runUnary = t2;
+ _.runBinary = t3;
+ _.registerCallback = t4;
+ _.registerUnaryCallback = t5;
+ _.registerBinaryCallback = t6;
+ _.errorCallback = t7;
+ _.scheduleMicrotask = t8;
+ _.createTimer = t9;
+ _.createPeriodicTimer = t10;
+ _.print = t11;
+ _.fork = t12;
+ },
+ _ZoneDelegate: function _ZoneDelegate(t0) {
+ this._delegationTarget = t0;
+ },
+ _Zone: function _Zone() {
+ },
+ _CustomZone: function _CustomZone(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) {
+ var _ = this;
+ _._run = t0;
+ _._runUnary = t1;
+ _._runBinary = t2;
+ _._registerCallback = t3;
+ _._registerUnaryCallback = t4;
+ _._registerBinaryCallback = t5;
+ _._errorCallback = t6;
+ _._scheduleMicrotask = t7;
+ _._createTimer = t8;
+ _._createPeriodicTimer = t9;
+ _._print = t10;
+ _._fork = t11;
+ _._handleUncaughtError = t12;
+ _._delegateCache = null;
+ _.parent = t13;
+ _._async$_map = t14;
+ },
+ _CustomZone_bindCallback_closure: function _CustomZone_bindCallback_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.registered = t1;
+ this.R = t2;
+ },
+ _CustomZone_bindUnaryCallback_closure: function _CustomZone_bindUnaryCallback_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.$this = t0;
+ _.registered = t1;
+ _.T = t2;
+ _.R = t3;
+ },
+ _CustomZone_bindCallbackGuarded_closure: function _CustomZone_bindCallbackGuarded_closure(t0, t1) {
+ this.$this = t0;
+ this.registered = t1;
+ },
+ _CustomZone_bindUnaryCallbackGuarded_closure: function _CustomZone_bindUnaryCallbackGuarded_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.registered = t1;
+ this.T = t2;
+ },
+ _rootHandleError_closure: function _rootHandleError_closure(t0, t1) {
+ this.error = t0;
+ this.stackTrace = t1;
+ },
+ _RootZone: function _RootZone() {
+ },
+ _RootZone_bindCallback_closure: function _RootZone_bindCallback_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.f = t1;
+ this.R = t2;
+ },
+ _RootZone_bindUnaryCallback_closure: function _RootZone_bindUnaryCallback_closure(t0, t1, t2, t3) {
+ var _ = this;
+ _.$this = t0;
+ _.f = t1;
+ _.T = t2;
+ _.R = t3;
+ },
+ _RootZone_bindCallbackGuarded_closure: function _RootZone_bindCallbackGuarded_closure(t0, t1) {
+ this.$this = t0;
+ this.f = t1;
+ },
+ _RootZone_bindUnaryCallbackGuarded_closure: function _RootZone_bindUnaryCallbackGuarded_closure(t0, t1, t2) {
+ this.$this = t0;
+ this.f = t1;
+ this.T = t2;
+ },
+ runZonedGuarded_closure: function runZonedGuarded_closure(t0, t1) {
+ this.parentZone = t0;
+ this.onError = t1;
+ },
+ HashMap_HashMap($K, $V) {
+ return new A._HashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("_HashMap<1,2>"));
+ },
+ _HashMap__getTableEntry(table, key) {
+ var entry = table[key];
+ return entry === table ? null : entry;
+ },
+ _HashMap__setTableEntry(table, key, value) {
+ if (value == null)
+ table[key] = table;
+ else
+ table[key] = value;
+ },
+ _HashMap__newHashTable() {
+ var table = Object.create(null);
+ A._HashMap__setTableEntry(table, "<non-identifier-key>", table);
+ delete table["<non-identifier-key>"];
+ return table;
+ },
+ LinkedHashMap_LinkedHashMap$_literal(keyValuePairs, $K, $V) {
+ return $K._eval$1("@<0>")._bind$1($V)._eval$1("LinkedHashMap<1,2>")._as(A.fillLiteralMap(keyValuePairs, new A.JsLinkedHashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("JsLinkedHashMap<1,2>"))));
+ },
+ LinkedHashMap_LinkedHashMap$_empty($K, $V) {
+ return new A.JsLinkedHashMap($K._eval$1("@<0>")._bind$1($V)._eval$1("JsLinkedHashMap<1,2>"));
+ },
+ LinkedHashSet_LinkedHashSet$_empty($E) {
+ return new A._LinkedHashSet($E._eval$1("_LinkedHashSet<0>"));
+ },
+ _LinkedHashSet__newHashTable() {
+ var table = Object.create(null);
+ table["<non-identifier-key>"] = table;
+ delete table["<non-identifier-key>"];
+ return table;
+ },
+ _LinkedHashSetIterator$(_set, _modifications, $E) {
+ var t1 = new A._LinkedHashSetIterator(_set, _modifications, $E._eval$1("_LinkedHashSetIterator<0>"));
+ t1._collection$_cell = _set._collection$_first;
+ return t1;
+ },
+ MapBase_mapToString(m) {
+ var result, t1 = {};
+ if (A.isToStringVisiting(m))
+ return "{...}";
+ result = new A.StringBuffer("");
+ try {
+ B.JSArray_methods.add$1($.toStringVisiting, m);
+ result._contents += "{";
+ t1.first = true;
+ J.forEach$1$x(m, new A.MapBase_mapToString_closure(t1, result));
+ result._contents += "}";
+ } finally {
+ if (0 >= $.toStringVisiting.length)
+ return A.ioore($.toStringVisiting, -1);
+ $.toStringVisiting.pop();
+ }
+ t1 = result._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _HashMap: function _HashMap(t0) {
+ var _ = this;
+ _._collection$_length = 0;
+ _._keys = _._collection$_rest = _._collection$_nums = _._collection$_strings = null;
+ _.$ti = t0;
+ },
+ _IdentityHashMap: function _IdentityHashMap(t0) {
+ var _ = this;
+ _._collection$_length = 0;
+ _._keys = _._collection$_rest = _._collection$_nums = _._collection$_strings = null;
+ _.$ti = t0;
+ },
+ _HashMapKeyIterable: function _HashMapKeyIterable(t0, t1) {
+ this._collection$_map = t0;
+ this.$ti = t1;
+ },
+ _HashMapKeyIterator: function _HashMapKeyIterator(t0, t1, t2) {
+ var _ = this;
+ _._collection$_map = t0;
+ _._keys = t1;
+ _._offset = 0;
+ _._collection$_current = null;
+ _.$ti = t2;
+ },
+ _LinkedHashSet: function _LinkedHashSet(t0) {
+ var _ = this;
+ _._collection$_length = 0;
+ _._collection$_last = _._collection$_first = _._collection$_rest = _._collection$_nums = _._collection$_strings = null;
+ _._collection$_modifications = 0;
+ _.$ti = t0;
+ },
+ _LinkedHashSetCell: function _LinkedHashSetCell(t0) {
+ this._element = t0;
+ this._collection$_previous = this._collection$_next = null;
+ },
+ _LinkedHashSetIterator: function _LinkedHashSetIterator(t0, t1, t2) {
+ var _ = this;
+ _._set = t0;
+ _._collection$_modifications = t1;
+ _._collection$_current = _._collection$_cell = null;
+ _.$ti = t2;
+ },
+ ListBase: function ListBase() {
+ },
+ MapBase: function MapBase() {
+ },
+ MapBase_mapToString_closure: function MapBase_mapToString_closure(t0, t1) {
+ this._box_0 = t0;
+ this.result = t1;
+ },
+ _UnmodifiableMapMixin: function _UnmodifiableMapMixin() {
+ },
+ MapView: function MapView() {
+ },
+ UnmodifiableMapView: function UnmodifiableMapView(t0, t1) {
+ this._collection$_map = t0;
+ this.$ti = t1;
+ },
+ SetBase: function SetBase() {
+ },
+ _SetBase: function _SetBase() {
+ },
+ _UnmodifiableMapView_MapView__UnmodifiableMapMixin: function _UnmodifiableMapView_MapView__UnmodifiableMapMixin() {
+ },
+ _parseJson(source, reviver) {
+ var e, exception, t1, parsed = null;
+ try {
+ parsed = JSON.parse(source);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ t1 = A.FormatException$(String(e), null, null);
+ throw A.wrapException(t1);
+ }
+ t1 = A._convertJsonToDartLazy(parsed);
+ return t1;
+ },
+ _convertJsonToDartLazy(object) {
+ var i;
+ if (object == null)
+ return null;
+ if (typeof object != "object")
+ return object;
+ if (Object.getPrototypeOf(object) !== Array.prototype)
+ return new A._JsonMap(object, Object.create(null));
+ for (i = 0; i < object.length; ++i)
+ object[i] = A._convertJsonToDartLazy(object[i]);
+ return object;
+ },
+ Utf8Decoder__convertIntercepted(allowMalformed, codeUnits, start, end) {
+ var casted, result;
+ if (codeUnits instanceof Uint8Array) {
+ casted = codeUnits;
+ end = casted.length;
+ if (end - start < 15)
+ return null;
+ result = A.Utf8Decoder__convertInterceptedUint8List(allowMalformed, casted, start, end);
+ if (result != null && allowMalformed)
+ if (result.indexOf("\ufffd") >= 0)
+ return null;
+ return result;
+ }
+ return null;
+ },
+ Utf8Decoder__convertInterceptedUint8List(allowMalformed, codeUnits, start, end) {
+ var decoder = allowMalformed ? $.$get$Utf8Decoder__decoderNonfatal() : $.$get$Utf8Decoder__decoder();
+ if (decoder == null)
+ return null;
+ if (0 === start && end === codeUnits.length)
+ return A.Utf8Decoder__useTextDecoder(decoder, codeUnits);
+ return A.Utf8Decoder__useTextDecoder(decoder, codeUnits.subarray(start, A.RangeError_checkValidRange(start, end, codeUnits.length)));
+ },
+ Utf8Decoder__useTextDecoder(decoder, codeUnits) {
+ var t1, exception;
+ try {
+ t1 = decoder.decode(codeUnits);
+ return t1;
+ } catch (exception) {
+ }
+ return null;
+ },
+ Base64Codec__checkPadding(source, sourceIndex, sourceEnd, firstPadding, paddingCount, $length) {
+ if (B.JSInt_methods.$mod($length, 4) !== 0)
+ throw A.wrapException(A.FormatException$("Invalid base64 padding, padded length must be multiple of four, is " + $length, source, sourceEnd));
+ if (firstPadding + paddingCount !== $length)
+ throw A.wrapException(A.FormatException$("Invalid base64 padding, '=' not at the end", source, sourceIndex));
+ if (paddingCount > 2)
+ throw A.wrapException(A.FormatException$("Invalid base64 padding, more than two '=' characters", source, sourceIndex));
+ },
+ JsonUnsupportedObjectError$(unsupportedObject, cause, partialResult) {
+ return new A.JsonUnsupportedObjectError(unsupportedObject, cause);
+ },
+ _defaultToEncodable(object) {
+ return object.toJson$0();
+ },
+ _JsonStringStringifier$(_sink, _toEncodable) {
+ return new A._JsonStringStringifier(_sink, [], A.convert___defaultToEncodable$closure());
+ },
+ _JsonStringStringifier_stringify(object, toEncodable, indent) {
+ var t1,
+ output = new A.StringBuffer("");
+ A._JsonStringStringifier_printOn(object, output, toEncodable, indent);
+ t1 = output._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _JsonStringStringifier_printOn(object, output, toEncodable, indent) {
+ var stringifier = A._JsonStringStringifier$(output, toEncodable);
+ stringifier.writeObject$1(object);
+ },
+ _Utf8Decoder_errorDescription(state) {
+ switch (state) {
+ case 65:
+ return "Missing extension byte";
+ case 67:
+ return "Unexpected extension byte";
+ case 69:
+ return "Invalid UTF-8 byte";
+ case 71:
+ return "Overlong encoding";
+ case 73:
+ return "Out of unicode range";
+ case 75:
+ return "Encoded surrogate";
+ case 77:
+ return "Unfinished UTF-8 octet sequence";
+ default:
+ return "";
+ }
+ },
+ _Utf8Decoder__makeUint8List(codeUnits, start, end) {
+ var t1, i, b,
+ $length = end - start,
+ bytes = new Uint8Array($length);
+ for (t1 = J.getInterceptor$asx(codeUnits), i = 0; i < $length; ++i) {
+ b = t1.$index(codeUnits, start + i);
+ if ((b & 4294967040) >>> 0 !== 0)
+ b = 255;
+ if (!(i < $length))
+ return A.ioore(bytes, i);
+ bytes[i] = b;
+ }
+ return bytes;
+ },
+ _JsonMap: function _JsonMap(t0, t1) {
+ this._original = t0;
+ this._processed = t1;
+ this._data = null;
+ },
+ _JsonMapKeyIterable: function _JsonMapKeyIterable(t0) {
+ this._convert$_parent = t0;
+ },
+ Utf8Decoder__decoder_closure: function Utf8Decoder__decoder_closure() {
+ },
+ Utf8Decoder__decoderNonfatal_closure: function Utf8Decoder__decoderNonfatal_closure() {
+ },
+ AsciiCodec: function AsciiCodec() {
+ },
+ _UnicodeSubsetEncoder: function _UnicodeSubsetEncoder() {
+ },
+ AsciiEncoder: function AsciiEncoder(t0) {
+ this._subsetMask = t0;
+ },
+ Base64Codec: function Base64Codec() {
+ },
+ Base64Encoder: function Base64Encoder() {
+ },
+ Codec: function Codec() {
+ },
+ _FusedCodec: function _FusedCodec(t0, t1, t2) {
+ this._convert$_first = t0;
+ this._second = t1;
+ this.$ti = t2;
+ },
+ Converter: function Converter() {
+ },
+ Encoding: function Encoding() {
+ },
+ JsonUnsupportedObjectError: function JsonUnsupportedObjectError(t0, t1) {
+ this.unsupportedObject = t0;
+ this.cause = t1;
+ },
+ JsonCyclicError: function JsonCyclicError(t0, t1) {
+ this.unsupportedObject = t0;
+ this.cause = t1;
+ },
+ JsonCodec: function JsonCodec() {
+ },
+ JsonEncoder: function JsonEncoder(t0) {
+ this._toEncodable = t0;
+ },
+ JsonDecoder: function JsonDecoder(t0) {
+ this._reviver = t0;
+ },
+ _JsonStringifier: function _JsonStringifier() {
+ },
+ _JsonStringifier_writeMap_closure: function _JsonStringifier_writeMap_closure(t0, t1) {
+ this._box_0 = t0;
+ this.keyValueList = t1;
+ },
+ _JsonStringStringifier: function _JsonStringStringifier(t0, t1, t2) {
+ this._sink = t0;
+ this._seen = t1;
+ this._toEncodable = t2;
+ },
+ Utf8Codec: function Utf8Codec() {
+ },
+ Utf8Encoder: function Utf8Encoder() {
+ },
+ _Utf8Encoder: function _Utf8Encoder(t0) {
+ this._bufferIndex = this._carry = 0;
+ this._buffer = t0;
+ },
+ Utf8Decoder: function Utf8Decoder(t0) {
+ this._allowMalformed = t0;
+ },
+ _Utf8Decoder: function _Utf8Decoder(t0) {
+ this.allowMalformed = t0;
+ this._state = 16;
+ this._charOrIndex = 0;
+ },
+ int_parse(source, radix) {
+ var value = A.Primitives_parseInt(source, radix);
+ if (value != null)
+ return value;
+ throw A.wrapException(A.FormatException$(source, null, null));
+ },
+ Error__throw(error, stackTrace) {
+ error = A.wrapException(error);
+ if (error == null)
+ error = type$.Object._as(error);
+ error.stack = stackTrace.toString$0(0);
+ throw error;
+ throw A.wrapException("unreachable");
+ },
+ List_List$filled($length, fill, growable, $E) {
+ var i,
+ result = growable ? J.JSArray_JSArray$growable($length, $E) : J.JSArray_JSArray$fixed($length, $E);
+ if ($length !== 0 && fill != null)
+ for (i = 0; i < result.length; ++i)
+ result[i] = fill;
+ return result;
+ },
+ List_List$from(elements, growable, $E) {
+ var t1,
+ list = A._setArrayType([], $E._eval$1("JSArray<0>"));
+ for (t1 = J.get$iterator$ax(elements); t1.moveNext$0();)
+ B.JSArray_methods.add$1(list, $E._as(t1.get$current(t1)));
+ if (growable)
+ return list;
+ return J.JSArray_markFixedList(list, $E);
+ },
+ List_List$of(elements, growable, $E) {
+ var t1;
+ if (growable)
+ return A.List_List$_of(elements, $E);
+ t1 = J.JSArray_markFixedList(A.List_List$_of(elements, $E), $E);
+ return t1;
+ },
+ List_List$_of(elements, $E) {
+ var list, t1;
+ if (Array.isArray(elements))
+ return A._setArrayType(elements.slice(0), $E._eval$1("JSArray<0>"));
+ list = A._setArrayType([], $E._eval$1("JSArray<0>"));
+ for (t1 = J.get$iterator$ax(elements); t1.moveNext$0();)
+ B.JSArray_methods.add$1(list, t1.get$current(t1));
+ return list;
+ },
+ List_List$unmodifiable(elements, $E) {
+ return J.JSArray_markUnmodifiableList(A.List_List$from(elements, false, $E));
+ },
+ String_String$fromCharCodes(charCodes, start, end) {
+ var array, len;
+ if (Array.isArray(charCodes)) {
+ array = charCodes;
+ len = array.length;
+ end = A.RangeError_checkValidRange(start, end, len);
+ return A.Primitives_stringFromCharCodes(start > 0 || end < len ? array.slice(start, end) : array);
+ }
+ if (type$.NativeUint8List._is(charCodes))
+ return A.Primitives_stringFromNativeUint8List(charCodes, start, A.RangeError_checkValidRange(start, end, charCodes.length));
+ return A.String__stringFromIterable(charCodes, start, end);
+ },
+ String_String$fromCharCode(charCode) {
+ return A.Primitives_stringFromCharCode(charCode);
+ },
+ String__stringFromIterable(charCodes, start, end) {
+ var t1, it, i, list, _null = null;
+ if (start < 0)
+ throw A.wrapException(A.RangeError$range(start, 0, J.get$length$asx(charCodes), _null, _null));
+ t1 = end == null;
+ if (!t1 && end < start)
+ throw A.wrapException(A.RangeError$range(end, start, J.get$length$asx(charCodes), _null, _null));
+ it = J.get$iterator$ax(charCodes);
+ for (i = 0; i < start; ++i)
+ if (!it.moveNext$0())
+ throw A.wrapException(A.RangeError$range(start, 0, i, _null, _null));
+ list = [];
+ if (t1)
+ for (; it.moveNext$0();)
+ list.push(it.get$current(it));
+ else
+ for (i = start; i < end; ++i) {
+ if (!it.moveNext$0())
+ throw A.wrapException(A.RangeError$range(end, start, i, _null, _null));
+ list.push(it.get$current(it));
+ }
+ return A.Primitives_stringFromCharCodes(list);
+ },
+ RegExp_RegExp(source, multiLine) {
+ return new A.JSSyntaxRegExp(source, A.JSSyntaxRegExp_makeNative(source, multiLine, true, false, false, false));
+ },
+ StringBuffer__writeAll(string, objects, separator) {
+ var iterator = J.get$iterator$ax(objects);
+ if (!iterator.moveNext$0())
+ return string;
+ if (separator.length === 0) {
+ do
+ string += A.S(iterator.get$current(iterator));
+ while (iterator.moveNext$0());
+ } else {
+ string += A.S(iterator.get$current(iterator));
+ for (; iterator.moveNext$0();)
+ string = string + separator + A.S(iterator.get$current(iterator));
+ }
+ return string;
+ },
+ NoSuchMethodError_NoSuchMethodError$withInvocation(receiver, invocation) {
+ return new A.NoSuchMethodError(receiver, invocation.get$memberName(), invocation.get$positionalArguments(), invocation.get$namedArguments());
+ },
+ Uri_base() {
+ var cachedUri, uri,
+ current = A.Primitives_currentUri();
+ if (current == null)
+ throw A.wrapException(A.UnsupportedError$("'Uri.base' is not supported"));
+ cachedUri = $.Uri__cachedBaseUri;
+ if (cachedUri != null && current === $.Uri__cachedBaseString)
+ return cachedUri;
+ uri = A.Uri_parse(current);
+ $.Uri__cachedBaseUri = uri;
+ $.Uri__cachedBaseString = current;
+ return uri;
+ },
+ _Uri__uriEncode(canonicalTable, text, encoding, spaceToPlus) {
+ var t1, bytes, i, t2, byte, t3,
+ _s16_ = "0123456789ABCDEF";
+ if (encoding === B.C_Utf8Codec) {
+ t1 = $.$get$_Uri__needsNoEncoding();
+ t1 = t1._nativeRegExp.test(text);
+ } else
+ t1 = false;
+ if (t1)
+ return text;
+ bytes = B.C_Utf8Encoder.convert$1(text);
+ for (t1 = bytes.length, i = 0, t2 = ""; i < t1; ++i) {
+ byte = bytes[i];
+ if (byte < 128) {
+ t3 = byte >>> 4;
+ if (!(t3 < 8))
+ return A.ioore(canonicalTable, t3);
+ t3 = (canonicalTable[t3] & 1 << (byte & 15)) !== 0;
+ } else
+ t3 = false;
+ if (t3)
+ t2 += A.Primitives_stringFromCharCode(byte);
+ else
+ t2 = spaceToPlus && byte === 32 ? t2 + "+" : t2 + "%" + _s16_[byte >>> 4 & 15] + _s16_[byte & 15];
+ }
+ return t2.charCodeAt(0) == 0 ? t2 : t2;
+ },
+ DateTime__fourDigits(n) {
+ var absN = Math.abs(n),
+ sign = n < 0 ? "-" : "";
+ if (absN >= 1000)
+ return "" + n;
+ if (absN >= 100)
+ return sign + "0" + absN;
+ if (absN >= 10)
+ return sign + "00" + absN;
+ return sign + "000" + absN;
+ },
+ DateTime__threeDigits(n) {
+ if (n >= 100)
+ return "" + n;
+ if (n >= 10)
+ return "0" + n;
+ return "00" + n;
+ },
+ DateTime__twoDigits(n) {
+ if (n >= 10)
+ return "" + n;
+ return "0" + n;
+ },
+ Error_safeToString(object) {
+ if (typeof object == "number" || A._isBool(object) || object == null)
+ return J.toString$0$(object);
+ if (typeof object == "string")
+ return JSON.stringify(object);
+ return A.Primitives_safeToString(object);
+ },
+ Error_throwWithStackTrace(error, stackTrace) {
+ A.checkNotNullable(error, "error", type$.Object);
+ A.checkNotNullable(stackTrace, "stackTrace", type$.StackTrace);
+ A.Error__throw(error, stackTrace);
+ },
+ AssertionError$(message) {
+ return new A.AssertionError(message);
+ },
+ ArgumentError$(message, $name) {
+ return new A.ArgumentError(false, null, $name, message);
+ },
+ ArgumentError$value(value, $name, message) {
+ return new A.ArgumentError(true, value, $name, message);
+ },
+ ArgumentError_checkNotNull(argument, $name, $T) {
+ return argument;
+ },
+ RangeError$value(value, $name) {
+ return new A.RangeError(null, null, true, value, $name, "Value not in range");
+ },
+ RangeError$range(invalidValue, minValue, maxValue, $name, message) {
+ return new A.RangeError(minValue, maxValue, true, invalidValue, $name, "Invalid value");
+ },
+ RangeError_checkValueInInterval(value, minValue, maxValue, $name) {
+ if (value < minValue || value > maxValue)
+ throw A.wrapException(A.RangeError$range(value, minValue, maxValue, $name, null));
+ return value;
+ },
+ RangeError_checkValidRange(start, end, $length) {
+ if (0 > start || start > $length)
+ throw A.wrapException(A.RangeError$range(start, 0, $length, "start", null));
+ if (end != null) {
+ if (start > end || end > $length)
+ throw A.wrapException(A.RangeError$range(end, start, $length, "end", null));
+ return end;
+ }
+ return $length;
+ },
+ RangeError_checkNotNegative(value, $name) {
+ if (value < 0)
+ throw A.wrapException(A.RangeError$range(value, 0, null, $name, null));
+ return value;
+ },
+ IndexError$withLength(invalidValue, $length, indexable, $name) {
+ return new A.IndexError($length, true, invalidValue, $name, "Index out of range");
+ },
+ UnsupportedError$(message) {
+ return new A.UnsupportedError(message);
+ },
+ UnimplementedError$(message) {
+ return new A.UnimplementedError(message);
+ },
+ StateError$(message) {
+ return new A.StateError(message);
+ },
+ ConcurrentModificationError$(modifiedObject) {
+ return new A.ConcurrentModificationError(modifiedObject);
+ },
+ FormatException$(message, source, offset) {
+ return new A.FormatException(message, source, offset);
+ },
+ Iterable_iterableToShortString(iterable, leftDelimiter, rightDelimiter) {
+ var parts, t1;
+ if (A.isToStringVisiting(iterable)) {
+ if (leftDelimiter === "(" && rightDelimiter === ")")
+ return "(...)";
+ return leftDelimiter + "..." + rightDelimiter;
+ }
+ parts = A._setArrayType([], type$.JSArray_String);
+ B.JSArray_methods.add$1($.toStringVisiting, iterable);
+ try {
+ A._iterablePartsToStrings(iterable, parts);
+ } finally {
+ if (0 >= $.toStringVisiting.length)
+ return A.ioore($.toStringVisiting, -1);
+ $.toStringVisiting.pop();
+ }
+ t1 = A.StringBuffer__writeAll(leftDelimiter, type$.Iterable_dynamic._as(parts), ", ") + rightDelimiter;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ Iterable_iterableToFullString(iterable, leftDelimiter, rightDelimiter) {
+ var buffer, t1;
+ if (A.isToStringVisiting(iterable))
+ return leftDelimiter + "..." + rightDelimiter;
+ buffer = new A.StringBuffer(leftDelimiter);
+ B.JSArray_methods.add$1($.toStringVisiting, iterable);
+ try {
+ t1 = buffer;
+ t1._contents = A.StringBuffer__writeAll(t1._contents, iterable, ", ");
+ } finally {
+ if (0 >= $.toStringVisiting.length)
+ return A.ioore($.toStringVisiting, -1);
+ $.toStringVisiting.pop();
+ }
+ buffer._contents += rightDelimiter;
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _iterablePartsToStrings(iterable, parts) {
+ var next, ultimateString, penultimateString, penultimate, ultimate, ultimate0, elision,
+ it = iterable.get$iterator(iterable),
+ $length = 0, count = 0;
+ while (true) {
+ if (!($length < 80 || count < 3))
+ break;
+ if (!it.moveNext$0())
+ return;
+ next = A.S(it.get$current(it));
+ B.JSArray_methods.add$1(parts, next);
+ $length += next.length + 2;
+ ++count;
+ }
+ if (!it.moveNext$0()) {
+ if (count <= 5)
+ return;
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ ultimateString = parts.pop();
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ penultimateString = parts.pop();
+ } else {
+ penultimate = it.get$current(it);
+ ++count;
+ if (!it.moveNext$0()) {
+ if (count <= 4) {
+ B.JSArray_methods.add$1(parts, A.S(penultimate));
+ return;
+ }
+ ultimateString = A.S(penultimate);
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ penultimateString = parts.pop();
+ $length += ultimateString.length + 2;
+ } else {
+ ultimate = it.get$current(it);
+ ++count;
+ for (; it.moveNext$0(); penultimate = ultimate, ultimate = ultimate0) {
+ ultimate0 = it.get$current(it);
+ ++count;
+ if (count > 100) {
+ while (true) {
+ if (!($length > 75 && count > 3))
+ break;
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ $length -= parts.pop().length + 2;
+ --count;
+ }
+ B.JSArray_methods.add$1(parts, "...");
+ return;
+ }
+ }
+ penultimateString = A.S(penultimate);
+ ultimateString = A.S(ultimate);
+ $length += ultimateString.length + penultimateString.length + 4;
+ }
+ }
+ if (count > parts.length + 2) {
+ $length += 5;
+ elision = "...";
+ } else
+ elision = null;
+ while (true) {
+ if (!($length > 80 && parts.length > 3))
+ break;
+ if (0 >= parts.length)
+ return A.ioore(parts, -1);
+ $length -= parts.pop().length + 2;
+ if (elision == null) {
+ $length += 5;
+ elision = "...";
+ }
+ }
+ if (elision != null)
+ B.JSArray_methods.add$1(parts, elision);
+ B.JSArray_methods.add$1(parts, penultimateString);
+ B.JSArray_methods.add$1(parts, ultimateString);
+ },
+ Object_hash(object1, object2, object3, object4) {
+ var t1;
+ if (B.C_SentinelValue === object3) {
+ t1 = J.get$hashCode$(object1);
+ object2 = J.get$hashCode$(object2);
+ return A.SystemHash_finish(A.SystemHash_combine(A.SystemHash_combine($.$get$_hashSeed(), t1), object2));
+ }
+ if (B.C_SentinelValue === object4) {
+ t1 = J.get$hashCode$(object1);
+ object2 = J.get$hashCode$(object2);
+ object3 = J.get$hashCode$(object3);
+ return A.SystemHash_finish(A.SystemHash_combine(A.SystemHash_combine(A.SystemHash_combine($.$get$_hashSeed(), t1), object2), object3));
+ }
+ t1 = J.get$hashCode$(object1);
+ object2 = J.get$hashCode$(object2);
+ object3 = J.get$hashCode$(object3);
+ object4 = J.get$hashCode$(object4);
+ object4 = A.SystemHash_finish(A.SystemHash_combine(A.SystemHash_combine(A.SystemHash_combine(A.SystemHash_combine($.$get$_hashSeed(), t1), object2), object3), object4));
+ return object4;
+ },
+ Uri_Uri$dataFromString($content) {
+ var t1, _null = null,
+ buffer = new A.StringBuffer(""),
+ indices = A._setArrayType([-1], type$.JSArray_int);
+ A.UriData__writeUri(_null, _null, _null, buffer, indices);
+ B.JSArray_methods.add$1(indices, buffer._contents.length);
+ buffer._contents += ",";
+ A.UriData__uriEncodeBytes(B.List_oFp, B.C_AsciiCodec.encode$1($content), buffer);
+ t1 = buffer._contents;
+ return new A.UriData(t1.charCodeAt(0) == 0 ? t1 : t1, indices, _null).get$uri();
+ },
+ Uri_parse(uri) {
+ var delta, indices, schemeEnd, hostStart, portStart, pathStart, queryStart, fragmentStart, isSimple, scheme, t1, t2, schemeAuth, queryStart0, pathStart0, userInfoStart, userInfo, host, portNumber, port, path, query, _null = null,
+ end = uri.length;
+ if (end >= 5) {
+ if (4 >= end)
+ return A.ioore(uri, 4);
+ delta = ((uri.charCodeAt(4) ^ 58) * 3 | uri.charCodeAt(0) ^ 100 | uri.charCodeAt(1) ^ 97 | uri.charCodeAt(2) ^ 116 | uri.charCodeAt(3) ^ 97) >>> 0;
+ if (delta === 0)
+ return A.UriData__parse(end < end ? B.JSString_methods.substring$2(uri, 0, end) : uri, 5, _null).get$uri();
+ else if (delta === 32)
+ return A.UriData__parse(B.JSString_methods.substring$2(uri, 5, end), 0, _null).get$uri();
+ }
+ indices = A.List_List$filled(8, 0, false, type$.int);
+ B.JSArray_methods.$indexSet(indices, 0, 0);
+ B.JSArray_methods.$indexSet(indices, 1, -1);
+ B.JSArray_methods.$indexSet(indices, 2, -1);
+ B.JSArray_methods.$indexSet(indices, 7, -1);
+ B.JSArray_methods.$indexSet(indices, 3, 0);
+ B.JSArray_methods.$indexSet(indices, 4, 0);
+ B.JSArray_methods.$indexSet(indices, 5, end);
+ B.JSArray_methods.$indexSet(indices, 6, end);
+ if (A._scan(uri, 0, end, 0, indices) >= 14)
+ B.JSArray_methods.$indexSet(indices, 7, end);
+ schemeEnd = indices[1];
+ if (schemeEnd >= 0)
+ if (A._scan(uri, 0, schemeEnd, 20, indices) === 20)
+ indices[7] = schemeEnd;
+ hostStart = indices[2] + 1;
+ portStart = indices[3];
+ pathStart = indices[4];
+ queryStart = indices[5];
+ fragmentStart = indices[6];
+ if (fragmentStart < queryStart)
+ queryStart = fragmentStart;
+ if (pathStart < hostStart)
+ pathStart = queryStart;
+ else if (pathStart <= schemeEnd)
+ pathStart = schemeEnd + 1;
+ if (portStart < hostStart)
+ portStart = pathStart;
+ isSimple = indices[7] < 0;
+ if (isSimple)
+ if (hostStart > schemeEnd + 3) {
+ scheme = _null;
+ isSimple = false;
+ } else {
+ t1 = portStart > 0;
+ if (t1 && portStart + 1 === pathStart) {
+ scheme = _null;
+ isSimple = false;
+ } else {
+ if (!B.JSString_methods.startsWith$2(uri, "\\", pathStart))
+ if (hostStart > 0)
+ t2 = B.JSString_methods.startsWith$2(uri, "\\", hostStart - 1) || B.JSString_methods.startsWith$2(uri, "\\", hostStart - 2);
+ else
+ t2 = false;
+ else
+ t2 = true;
+ if (t2) {
+ scheme = _null;
+ isSimple = false;
+ } else {
+ if (!(queryStart < end && queryStart === pathStart + 2 && B.JSString_methods.startsWith$2(uri, "..", pathStart)))
+ t2 = queryStart > pathStart + 2 && B.JSString_methods.startsWith$2(uri, "/..", queryStart - 3);
+ else
+ t2 = true;
+ if (t2) {
+ scheme = _null;
+ isSimple = false;
+ } else {
+ if (schemeEnd === 4)
+ if (B.JSString_methods.startsWith$2(uri, "file", 0)) {
+ if (hostStart <= 0) {
+ if (!B.JSString_methods.startsWith$2(uri, "/", pathStart)) {
+ schemeAuth = "file:///";
+ delta = 3;
+ } else {
+ schemeAuth = "file://";
+ delta = 2;
+ }
+ uri = schemeAuth + B.JSString_methods.substring$2(uri, pathStart, end);
+ schemeEnd -= 0;
+ t1 = delta - 0;
+ queryStart += t1;
+ fragmentStart += t1;
+ end = uri.length;
+ hostStart = 7;
+ portStart = 7;
+ pathStart = 7;
+ } else if (pathStart === queryStart) {
+ ++fragmentStart;
+ queryStart0 = queryStart + 1;
+ uri = B.JSString_methods.replaceRange$3(uri, pathStart, queryStart, "/");
+ ++end;
+ queryStart = queryStart0;
+ }
+ scheme = "file";
+ } else if (B.JSString_methods.startsWith$2(uri, "http", 0)) {
+ if (t1 && portStart + 3 === pathStart && B.JSString_methods.startsWith$2(uri, "80", portStart + 1)) {
+ fragmentStart -= 3;
+ pathStart0 = pathStart - 3;
+ queryStart -= 3;
+ uri = B.JSString_methods.replaceRange$3(uri, portStart, pathStart, "");
+ end -= 3;
+ pathStart = pathStart0;
+ }
+ scheme = "http";
+ } else
+ scheme = _null;
+ else if (schemeEnd === 5 && B.JSString_methods.startsWith$2(uri, "https", 0)) {
+ if (t1 && portStart + 4 === pathStart && B.JSString_methods.startsWith$2(uri, "443", portStart + 1)) {
+ fragmentStart -= 4;
+ pathStart0 = pathStart - 4;
+ queryStart -= 4;
+ uri = B.JSString_methods.replaceRange$3(uri, portStart, pathStart, "");
+ end -= 3;
+ pathStart = pathStart0;
+ }
+ scheme = "https";
+ } else
+ scheme = _null;
+ isSimple = true;
+ }
+ }
+ }
+ }
+ else
+ scheme = _null;
+ if (isSimple) {
+ if (end < uri.length) {
+ uri = B.JSString_methods.substring$2(uri, 0, end);
+ schemeEnd -= 0;
+ hostStart -= 0;
+ portStart -= 0;
+ pathStart -= 0;
+ queryStart -= 0;
+ fragmentStart -= 0;
+ }
+ return new A._SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart, queryStart, fragmentStart, scheme);
+ }
+ if (scheme == null)
+ if (schemeEnd > 0)
+ scheme = A._Uri__makeScheme(uri, 0, schemeEnd);
+ else {
+ if (schemeEnd === 0)
+ A._Uri__fail(uri, 0, "Invalid empty scheme");
+ scheme = "";
+ }
+ if (hostStart > 0) {
+ userInfoStart = schemeEnd + 3;
+ userInfo = userInfoStart < hostStart ? A._Uri__makeUserInfo(uri, userInfoStart, hostStart - 1) : "";
+ host = A._Uri__makeHost(uri, hostStart, portStart, false);
+ t1 = portStart + 1;
+ if (t1 < pathStart) {
+ portNumber = A.Primitives_parseInt(B.JSString_methods.substring$2(uri, t1, pathStart), _null);
+ port = A._Uri__makePort(portNumber == null ? A.throwExpression(A.FormatException$("Invalid port", uri, t1)) : portNumber, scheme);
+ } else
+ port = _null;
+ } else {
+ port = _null;
+ host = port;
+ userInfo = "";
+ }
+ path = A._Uri__makePath(uri, pathStart, queryStart, _null, scheme, host != null);
+ query = queryStart < fragmentStart ? A._Uri__makeQuery(uri, queryStart + 1, fragmentStart, _null) : _null;
+ return A._Uri$_internal(scheme, userInfo, host, port, path, query, fragmentStart < end ? A._Uri__makeFragment(uri, fragmentStart + 1, end) : _null);
+ },
+ Uri_decodeComponent(encodedComponent) {
+ A._asString(encodedComponent);
+ return A._Uri__uriDecode(encodedComponent, 0, encodedComponent.length, B.C_Utf8Codec, false);
+ },
+ Uri_splitQueryString(query) {
+ var t1 = type$.String;
+ return B.JSArray_methods.fold$1$2(A._setArrayType(query.split("&"), type$.JSArray_String), A.LinkedHashMap_LinkedHashMap$_empty(t1, t1), new A.Uri_splitQueryString_closure(B.C_Utf8Codec), type$.Map_String_String);
+ },
+ Uri__parseIPv4Address(host, start, end) {
+ var t1, i, partStart, partIndex, char, part, partIndex0,
+ _s43_ = "IPv4 address should contain exactly 4 parts",
+ _s37_ = "each part must be in the range 0..255",
+ error = new A.Uri__parseIPv4Address_error(host),
+ result = new Uint8Array(4);
+ for (t1 = host.length, i = start, partStart = i, partIndex = 0; i < end; ++i) {
+ if (!(i >= 0 && i < t1))
+ return A.ioore(host, i);
+ char = host.charCodeAt(i);
+ if (char !== 46) {
+ if ((char ^ 48) > 9)
+ error.call$2("invalid character", i);
+ } else {
+ if (partIndex === 3)
+ error.call$2(_s43_, i);
+ part = A.int_parse(B.JSString_methods.substring$2(host, partStart, i), null);
+ if (part > 255)
+ error.call$2(_s37_, partStart);
+ partIndex0 = partIndex + 1;
+ if (!(partIndex < 4))
+ return A.ioore(result, partIndex);
+ result[partIndex] = part;
+ partStart = i + 1;
+ partIndex = partIndex0;
+ }
+ }
+ if (partIndex !== 3)
+ error.call$2(_s43_, end);
+ part = A.int_parse(B.JSString_methods.substring$2(host, partStart, end), null);
+ if (part > 255)
+ error.call$2(_s37_, partStart);
+ if (!(partIndex < 4))
+ return A.ioore(result, partIndex);
+ result[partIndex] = part;
+ return result;
+ },
+ Uri_parseIPv6Address(host, start, end) {
+ var parts, i, partStart, wildcardSeen, seenDot, char, atEnd, last, bytes, wildCardLength, index, value, j, t2, _null = null,
+ error = new A.Uri_parseIPv6Address_error(host),
+ parseHex = new A.Uri_parseIPv6Address_parseHex(error, host),
+ t1 = host.length;
+ if (t1 < 2)
+ error.call$2("address is too short", _null);
+ parts = A._setArrayType([], type$.JSArray_int);
+ for (i = start, partStart = i, wildcardSeen = false, seenDot = false; i < end; ++i) {
+ if (!(i >= 0 && i < t1))
+ return A.ioore(host, i);
+ char = host.charCodeAt(i);
+ if (char === 58) {
+ if (i === start) {
+ ++i;
+ if (!(i < t1))
+ return A.ioore(host, i);
+ if (host.charCodeAt(i) !== 58)
+ error.call$2("invalid start colon.", i);
+ partStart = i;
+ }
+ if (i === partStart) {
+ if (wildcardSeen)
+ error.call$2("only one wildcard `::` is allowed", i);
+ B.JSArray_methods.add$1(parts, -1);
+ wildcardSeen = true;
+ } else
+ B.JSArray_methods.add$1(parts, parseHex.call$2(partStart, i));
+ partStart = i + 1;
+ } else if (char === 46)
+ seenDot = true;
+ }
+ if (parts.length === 0)
+ error.call$2("too few parts", _null);
+ atEnd = partStart === end;
+ t1 = B.JSArray_methods.get$last(parts);
+ if (atEnd && t1 !== -1)
+ error.call$2("expected a part after last `:`", end);
+ if (!atEnd)
+ if (!seenDot)
+ B.JSArray_methods.add$1(parts, parseHex.call$2(partStart, end));
+ else {
+ last = A.Uri__parseIPv4Address(host, partStart, end);
+ B.JSArray_methods.add$1(parts, (last[0] << 8 | last[1]) >>> 0);
+ B.JSArray_methods.add$1(parts, (last[2] << 8 | last[3]) >>> 0);
+ }
+ if (wildcardSeen) {
+ if (parts.length > 7)
+ error.call$2("an address with a wildcard must have less than 7 parts", _null);
+ } else if (parts.length !== 8)
+ error.call$2("an address without a wildcard must contain exactly 8 parts", _null);
+ bytes = new Uint8Array(16);
+ for (t1 = parts.length, wildCardLength = 9 - t1, i = 0, index = 0; i < t1; ++i) {
+ value = parts[i];
+ if (value === -1)
+ for (j = 0; j < wildCardLength; ++j) {
+ if (!(index >= 0 && index < 16))
+ return A.ioore(bytes, index);
+ bytes[index] = 0;
+ t2 = index + 1;
+ if (!(t2 < 16))
+ return A.ioore(bytes, t2);
+ bytes[t2] = 0;
+ index += 2;
+ }
+ else {
+ t2 = B.JSInt_methods._shrOtherPositive$1(value, 8);
+ if (!(index >= 0 && index < 16))
+ return A.ioore(bytes, index);
+ bytes[index] = t2;
+ t2 = index + 1;
+ if (!(t2 < 16))
+ return A.ioore(bytes, t2);
+ bytes[t2] = value & 255;
+ index += 2;
+ }
+ }
+ return bytes;
+ },
+ _Uri$_internal(scheme, _userInfo, _host, _port, path, _query, _fragment) {
+ return new A._Uri(scheme, _userInfo, _host, _port, path, _query, _fragment);
+ },
+ _Uri__Uri(host, path, pathSegments, scheme) {
+ var userInfo, query, fragment, port, isFile, t1, hasAuthority, t2, _null = null;
+ scheme = scheme == null ? "" : A._Uri__makeScheme(scheme, 0, scheme.length);
+ userInfo = A._Uri__makeUserInfo(_null, 0, 0);
+ host = A._Uri__makeHost(host, 0, host == null ? 0 : host.length, false);
+ query = A._Uri__makeQuery(_null, 0, 0, _null);
+ fragment = A._Uri__makeFragment(_null, 0, 0);
+ port = A._Uri__makePort(_null, scheme);
+ isFile = scheme === "file";
+ if (host == null)
+ t1 = userInfo.length !== 0 || port != null || isFile;
+ else
+ t1 = false;
+ if (t1)
+ host = "";
+ t1 = host == null;
+ hasAuthority = !t1;
+ path = A._Uri__makePath(path, 0, path == null ? 0 : path.length, pathSegments, scheme, hasAuthority);
+ t2 = scheme.length === 0;
+ if (t2 && t1 && !B.JSString_methods.startsWith$1(path, "/"))
+ path = A._Uri__normalizeRelativePath(path, !t2 || hasAuthority);
+ else
+ path = A._Uri__removeDotSegments(path);
+ return A._Uri$_internal(scheme, userInfo, t1 && B.JSString_methods.startsWith$1(path, "//") ? "" : host, port, path, query, fragment);
+ },
+ _Uri__defaultPort(scheme) {
+ if (scheme === "http")
+ return 80;
+ if (scheme === "https")
+ return 443;
+ return 0;
+ },
+ _Uri__fail(uri, index, message) {
+ throw A.wrapException(A.FormatException$(message, uri, index));
+ },
+ _Uri__Uri$file(path, windows) {
+ return windows ? A._Uri__makeWindowsFileUrl(path, false) : A._Uri__makeFileUri(path, false);
+ },
+ _Uri__checkNonWindowsPathReservedCharacters(segments, argumentError) {
+ var t1, _i, segment;
+ for (t1 = segments.length, _i = 0; _i < t1; ++_i) {
+ segment = segments[_i];
+ if (J.contains$1$asx(segment, "/")) {
+ t1 = A.UnsupportedError$("Illegal path character " + A.S(segment));
+ throw A.wrapException(t1);
+ }
+ }
+ },
+ _Uri__checkWindowsPathReservedCharacters(segments, argumentError, firstSegment) {
+ var t1, t2, t3;
+ for (t1 = A.SubListIterable$(segments, firstSegment, null, A._arrayInstanceType(segments)._precomputed1), t2 = t1.$ti, t1 = new A.ListIterator(t1, t1.get$length(t1), t2._eval$1("ListIterator<ListIterable.E>")), t2 = t2._eval$1("ListIterable.E"); t1.moveNext$0();) {
+ t3 = t1.__internal$_current;
+ if (t3 == null)
+ t3 = t2._as(t3);
+ if (B.JSString_methods.contains$1(t3, A.RegExp_RegExp('["*/:<>?\\\\|]', false)))
+ if (argumentError)
+ throw A.wrapException(A.ArgumentError$("Illegal character in path", null));
+ else
+ throw A.wrapException(A.UnsupportedError$("Illegal character in path: " + t3));
+ }
+ },
+ _Uri__checkWindowsDriveLetter(charCode, argumentError) {
+ var t1,
+ _s21_ = "Illegal drive letter ";
+ if (!(65 <= charCode && charCode <= 90))
+ t1 = 97 <= charCode && charCode <= 122;
+ else
+ t1 = true;
+ if (t1)
+ return;
+ if (argumentError)
+ throw A.wrapException(A.ArgumentError$(_s21_ + A.String_String$fromCharCode(charCode), null));
+ else
+ throw A.wrapException(A.UnsupportedError$(_s21_ + A.String_String$fromCharCode(charCode)));
+ },
+ _Uri__makeFileUri(path, slashTerminated) {
+ var _null = null,
+ segments = A._setArrayType(path.split("/"), type$.JSArray_String);
+ if (B.JSString_methods.startsWith$1(path, "/"))
+ return A._Uri__Uri(_null, _null, segments, "file");
+ else
+ return A._Uri__Uri(_null, _null, segments, _null);
+ },
+ _Uri__makeWindowsFileUrl(path, slashTerminated) {
+ var t1, pathSegments, pathStart, hostPart, _s1_ = "\\", _null = null, _s4_ = "file";
+ if (B.JSString_methods.startsWith$1(path, "\\\\?\\"))
+ if (B.JSString_methods.startsWith$2(path, "UNC\\", 4))
+ path = B.JSString_methods.replaceRange$3(path, 0, 7, _s1_);
+ else {
+ path = B.JSString_methods.substring$1(path, 4);
+ t1 = path.length;
+ if (t1 >= 3) {
+ if (1 >= t1)
+ return A.ioore(path, 1);
+ if (path.charCodeAt(1) === 58) {
+ if (2 >= t1)
+ return A.ioore(path, 2);
+ t1 = path.charCodeAt(2) !== 92;
+ } else
+ t1 = true;
+ } else
+ t1 = true;
+ if (t1)
+ throw A.wrapException(A.ArgumentError$value(path, "path", "Windows paths with \\\\?\\ prefix must be absolute"));
+ }
+ else
+ path = A.stringReplaceAllUnchecked(path, "/", _s1_);
+ t1 = path.length;
+ if (t1 > 1 && path.charCodeAt(1) === 58) {
+ if (0 >= t1)
+ return A.ioore(path, 0);
+ A._Uri__checkWindowsDriveLetter(path.charCodeAt(0), true);
+ if (t1 !== 2) {
+ if (2 >= t1)
+ return A.ioore(path, 2);
+ t1 = path.charCodeAt(2) !== 92;
+ } else
+ t1 = true;
+ if (t1)
+ throw A.wrapException(A.ArgumentError$value(path, "path", "Windows paths with drive letter must be absolute"));
+ pathSegments = A._setArrayType(path.split(_s1_), type$.JSArray_String);
+ A._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 1);
+ return A._Uri__Uri(_null, _null, pathSegments, _s4_);
+ }
+ if (B.JSString_methods.startsWith$1(path, _s1_))
+ if (B.JSString_methods.startsWith$2(path, _s1_, 1)) {
+ pathStart = B.JSString_methods.indexOf$2(path, _s1_, 2);
+ t1 = pathStart < 0;
+ hostPart = t1 ? B.JSString_methods.substring$1(path, 2) : B.JSString_methods.substring$2(path, 2, pathStart);
+ pathSegments = A._setArrayType((t1 ? "" : B.JSString_methods.substring$1(path, pathStart + 1)).split(_s1_), type$.JSArray_String);
+ A._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 0);
+ return A._Uri__Uri(hostPart, _null, pathSegments, _s4_);
+ } else {
+ pathSegments = A._setArrayType(path.split(_s1_), type$.JSArray_String);
+ A._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 0);
+ return A._Uri__Uri(_null, _null, pathSegments, _s4_);
+ }
+ else {
+ pathSegments = A._setArrayType(path.split(_s1_), type$.JSArray_String);
+ A._Uri__checkWindowsPathReservedCharacters(pathSegments, true, 0);
+ return A._Uri__Uri(_null, _null, pathSegments, _null);
+ }
+ },
+ _Uri__makePort(port, scheme) {
+ if (port != null && port === A._Uri__defaultPort(scheme))
+ return null;
+ return port;
+ },
+ _Uri__makeHost(host, start, end, strictIPv6) {
+ var t1, t2, index, zoneIDstart, zoneID, i;
+ if (host == null)
+ return null;
+ if (start === end)
+ return "";
+ t1 = host.length;
+ if (!(start >= 0 && start < t1))
+ return A.ioore(host, start);
+ if (host.charCodeAt(start) === 91) {
+ t2 = end - 1;
+ if (!(t2 >= 0 && t2 < t1))
+ return A.ioore(host, t2);
+ if (host.charCodeAt(t2) !== 93)
+ A._Uri__fail(host, start, "Missing end `]` to match `[` in host");
+ t1 = start + 1;
+ index = A._Uri__checkZoneID(host, t1, t2);
+ if (index < t2) {
+ zoneIDstart = index + 1;
+ zoneID = A._Uri__normalizeZoneID(host, B.JSString_methods.startsWith$2(host, "25", zoneIDstart) ? index + 3 : zoneIDstart, t2, "%25");
+ } else
+ zoneID = "";
+ A.Uri_parseIPv6Address(host, t1, index);
+ return B.JSString_methods.substring$2(host, start, index).toLowerCase() + zoneID + "]";
+ }
+ for (i = start; i < end; ++i) {
+ if (!(i < t1))
+ return A.ioore(host, i);
+ if (host.charCodeAt(i) === 58) {
+ index = B.JSString_methods.indexOf$2(host, "%", start);
+ index = index >= start && index < end ? index : end;
+ if (index < end) {
+ zoneIDstart = index + 1;
+ zoneID = A._Uri__normalizeZoneID(host, B.JSString_methods.startsWith$2(host, "25", zoneIDstart) ? index + 3 : zoneIDstart, end, "%25");
+ } else
+ zoneID = "";
+ A.Uri_parseIPv6Address(host, start, index);
+ return "[" + B.JSString_methods.substring$2(host, start, index) + zoneID + "]";
+ }
+ }
+ return A._Uri__normalizeRegName(host, start, end);
+ },
+ _Uri__checkZoneID(host, start, end) {
+ var index = B.JSString_methods.indexOf$2(host, "%", start);
+ return index >= start && index < end ? index : end;
+ },
+ _Uri__normalizeZoneID(host, start, end, prefix) {
+ var t1, index, sectionStart, isNormalized, char, replacement, t2, t3, tail, sourceLength, slice,
+ buffer = prefix !== "" ? new A.StringBuffer(prefix) : null;
+ for (t1 = host.length, index = start, sectionStart = index, isNormalized = true; index < end;) {
+ if (!(index >= 0 && index < t1))
+ return A.ioore(host, index);
+ char = host.charCodeAt(index);
+ if (char === 37) {
+ replacement = A._Uri__normalizeEscape(host, index, true);
+ t2 = replacement == null;
+ if (t2 && isNormalized) {
+ index += 3;
+ continue;
+ }
+ if (buffer == null)
+ buffer = new A.StringBuffer("");
+ t3 = buffer._contents += B.JSString_methods.substring$2(host, sectionStart, index);
+ if (t2)
+ replacement = B.JSString_methods.substring$2(host, index, index + 3);
+ else if (replacement === "%")
+ A._Uri__fail(host, index, "ZoneID should not contain % anymore");
+ buffer._contents = t3 + replacement;
+ index += 3;
+ sectionStart = index;
+ isNormalized = true;
+ } else {
+ if (char < 127) {
+ t2 = char >>> 4;
+ if (!(t2 < 8))
+ return A.ioore(B.List_M1A, t2);
+ t2 = (B.List_M1A[t2] & 1 << (char & 15)) !== 0;
+ } else
+ t2 = false;
+ if (t2) {
+ if (isNormalized && 65 <= char && 90 >= char) {
+ if (buffer == null)
+ buffer = new A.StringBuffer("");
+ if (sectionStart < index) {
+ buffer._contents += B.JSString_methods.substring$2(host, sectionStart, index);
+ sectionStart = index;
+ }
+ isNormalized = false;
+ }
+ ++index;
+ } else {
+ if ((char & 64512) === 55296 && index + 1 < end) {
+ t2 = index + 1;
+ if (!(t2 < t1))
+ return A.ioore(host, t2);
+ tail = host.charCodeAt(t2);
+ if ((tail & 64512) === 56320) {
+ char = (char & 1023) << 10 | tail & 1023 | 65536;
+ sourceLength = 2;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ slice = B.JSString_methods.substring$2(host, sectionStart, index);
+ if (buffer == null) {
+ buffer = new A.StringBuffer("");
+ t2 = buffer;
+ } else
+ t2 = buffer;
+ t2._contents += slice;
+ t2._contents += A._Uri__escapeChar(char);
+ index += sourceLength;
+ sectionStart = index;
+ }
+ }
+ }
+ if (buffer == null)
+ return B.JSString_methods.substring$2(host, start, end);
+ if (sectionStart < end)
+ buffer._contents += B.JSString_methods.substring$2(host, sectionStart, end);
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__normalizeRegName(host, start, end) {
+ var t1, index, sectionStart, buffer, isNormalized, char, replacement, t2, slice, t3, sourceLength, tail;
+ for (t1 = host.length, index = start, sectionStart = index, buffer = null, isNormalized = true; index < end;) {
+ if (!(index >= 0 && index < t1))
+ return A.ioore(host, index);
+ char = host.charCodeAt(index);
+ if (char === 37) {
+ replacement = A._Uri__normalizeEscape(host, index, true);
+ t2 = replacement == null;
+ if (t2 && isNormalized) {
+ index += 3;
+ continue;
+ }
+ if (buffer == null)
+ buffer = new A.StringBuffer("");
+ slice = B.JSString_methods.substring$2(host, sectionStart, index);
+ t3 = buffer._contents += !isNormalized ? slice.toLowerCase() : slice;
+ if (t2) {
+ replacement = B.JSString_methods.substring$2(host, index, index + 3);
+ sourceLength = 3;
+ } else if (replacement === "%") {
+ replacement = "%25";
+ sourceLength = 1;
+ } else
+ sourceLength = 3;
+ buffer._contents = t3 + replacement;
+ index += sourceLength;
+ sectionStart = index;
+ isNormalized = true;
+ } else {
+ if (char < 127) {
+ t2 = char >>> 4;
+ if (!(t2 < 8))
+ return A.ioore(B.List_ejq, t2);
+ t2 = (B.List_ejq[t2] & 1 << (char & 15)) !== 0;
+ } else
+ t2 = false;
+ if (t2) {
+ if (isNormalized && 65 <= char && 90 >= char) {
+ if (buffer == null)
+ buffer = new A.StringBuffer("");
+ if (sectionStart < index) {
+ buffer._contents += B.JSString_methods.substring$2(host, sectionStart, index);
+ sectionStart = index;
+ }
+ isNormalized = false;
+ }
+ ++index;
+ } else {
+ if (char <= 93) {
+ t2 = char >>> 4;
+ if (!(t2 < 8))
+ return A.ioore(B.List_YmH, t2);
+ t2 = (B.List_YmH[t2] & 1 << (char & 15)) !== 0;
+ } else
+ t2 = false;
+ if (t2)
+ A._Uri__fail(host, index, "Invalid character");
+ else {
+ if ((char & 64512) === 55296 && index + 1 < end) {
+ t2 = index + 1;
+ if (!(t2 < t1))
+ return A.ioore(host, t2);
+ tail = host.charCodeAt(t2);
+ if ((tail & 64512) === 56320) {
+ char = (char & 1023) << 10 | tail & 1023 | 65536;
+ sourceLength = 2;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ slice = B.JSString_methods.substring$2(host, sectionStart, index);
+ if (!isNormalized)
+ slice = slice.toLowerCase();
+ if (buffer == null) {
+ buffer = new A.StringBuffer("");
+ t2 = buffer;
+ } else
+ t2 = buffer;
+ t2._contents += slice;
+ t2._contents += A._Uri__escapeChar(char);
+ index += sourceLength;
+ sectionStart = index;
+ }
+ }
+ }
+ }
+ if (buffer == null)
+ return B.JSString_methods.substring$2(host, start, end);
+ if (sectionStart < end) {
+ slice = B.JSString_methods.substring$2(host, sectionStart, end);
+ buffer._contents += !isNormalized ? slice.toLowerCase() : slice;
+ }
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__makeScheme(scheme, start, end) {
+ var t1, i, containsUpperCase, codeUnit, t2;
+ if (start === end)
+ return "";
+ t1 = scheme.length;
+ if (!(start < t1))
+ return A.ioore(scheme, start);
+ if (!A._Uri__isAlphabeticCharacter(scheme.charCodeAt(start)))
+ A._Uri__fail(scheme, start, "Scheme not starting with alphabetic character");
+ for (i = start, containsUpperCase = false; i < end; ++i) {
+ if (!(i < t1))
+ return A.ioore(scheme, i);
+ codeUnit = scheme.charCodeAt(i);
+ if (codeUnit < 128) {
+ t2 = codeUnit >>> 4;
+ if (!(t2 < 8))
+ return A.ioore(B.List_MMm, t2);
+ t2 = (B.List_MMm[t2] & 1 << (codeUnit & 15)) !== 0;
+ } else
+ t2 = false;
+ if (!t2)
+ A._Uri__fail(scheme, i, "Illegal scheme character");
+ if (65 <= codeUnit && codeUnit <= 90)
+ containsUpperCase = true;
+ }
+ scheme = B.JSString_methods.substring$2(scheme, start, end);
+ return A._Uri__canonicalizeScheme(containsUpperCase ? scheme.toLowerCase() : scheme);
+ },
+ _Uri__canonicalizeScheme(scheme) {
+ if (scheme === "http")
+ return "http";
+ if (scheme === "file")
+ return "file";
+ if (scheme === "https")
+ return "https";
+ if (scheme === "package")
+ return "package";
+ return scheme;
+ },
+ _Uri__makeUserInfo(userInfo, start, end) {
+ if (userInfo == null)
+ return "";
+ return A._Uri__normalizeOrSubstring(userInfo, start, end, B.List_OL3, false, false);
+ },
+ _Uri__makePath(path, start, end, pathSegments, scheme, hasAuthority) {
+ var t1, result,
+ isFile = scheme === "file",
+ ensureLeadingSlash = isFile || hasAuthority;
+ if (path == null) {
+ if (pathSegments == null)
+ return isFile ? "/" : "";
+ t1 = A._arrayInstanceType(pathSegments);
+ result = new A.MappedListIterable(pathSegments, t1._eval$1("String(1)")._as(new A._Uri__makePath_closure()), t1._eval$1("MappedListIterable<1,String>")).join$1(0, "/");
+ } else if (pathSegments != null)
+ throw A.wrapException(A.ArgumentError$("Both path and pathSegments specified", null));
+ else
+ result = A._Uri__normalizeOrSubstring(path, start, end, B.List_XRg, true, true);
+ if (result.length === 0) {
+ if (isFile)
+ return "/";
+ } else if (ensureLeadingSlash && !B.JSString_methods.startsWith$1(result, "/"))
+ result = "/" + result;
+ return A._Uri__normalizePath(result, scheme, hasAuthority);
+ },
+ _Uri__normalizePath(path, scheme, hasAuthority) {
+ var t1 = scheme.length === 0;
+ if (t1 && !hasAuthority && !B.JSString_methods.startsWith$1(path, "/") && !B.JSString_methods.startsWith$1(path, "\\"))
+ return A._Uri__normalizeRelativePath(path, !t1 || hasAuthority);
+ return A._Uri__removeDotSegments(path);
+ },
+ _Uri__makeQuery(query, start, end, queryParameters) {
+ if (query != null)
+ return A._Uri__normalizeOrSubstring(query, start, end, B.List_oFp, true, false);
+ return null;
+ },
+ _Uri__makeFragment(fragment, start, end) {
+ if (fragment == null)
+ return null;
+ return A._Uri__normalizeOrSubstring(fragment, start, end, B.List_oFp, true, false);
+ },
+ _Uri__normalizeEscape(source, index, lowerCase) {
+ var t3, firstDigit, secondDigit, firstDigitValue, secondDigitValue, value,
+ t1 = index + 2,
+ t2 = source.length;
+ if (t1 >= t2)
+ return "%";
+ t3 = index + 1;
+ if (!(t3 >= 0 && t3 < t2))
+ return A.ioore(source, t3);
+ firstDigit = source.charCodeAt(t3);
+ if (!(t1 >= 0))
+ return A.ioore(source, t1);
+ secondDigit = source.charCodeAt(t1);
+ firstDigitValue = A.hexDigitValue(firstDigit);
+ secondDigitValue = A.hexDigitValue(secondDigit);
+ if (firstDigitValue < 0 || secondDigitValue < 0)
+ return "%";
+ value = firstDigitValue * 16 + secondDigitValue;
+ if (value < 127) {
+ t1 = B.JSInt_methods._shrOtherPositive$1(value, 4);
+ if (!(t1 < 8))
+ return A.ioore(B.List_M1A, t1);
+ t1 = (B.List_M1A[t1] & 1 << (value & 15)) !== 0;
+ } else
+ t1 = false;
+ if (t1)
+ return A.Primitives_stringFromCharCode(lowerCase && 65 <= value && 90 >= value ? (value | 32) >>> 0 : value);
+ if (firstDigit >= 97 || secondDigit >= 97)
+ return B.JSString_methods.substring$2(source, index, index + 3).toUpperCase();
+ return null;
+ },
+ _Uri__escapeChar(char) {
+ var codeUnits, t1, flag, encodedBytes, index, byte, t2, t3,
+ _s16_ = "0123456789ABCDEF";
+ if (char < 128) {
+ codeUnits = new Uint8Array(3);
+ codeUnits[0] = 37;
+ t1 = char >>> 4;
+ if (!(t1 < 16))
+ return A.ioore(_s16_, t1);
+ codeUnits[1] = _s16_.charCodeAt(t1);
+ codeUnits[2] = _s16_.charCodeAt(char & 15);
+ } else {
+ if (char > 2047)
+ if (char > 65535) {
+ flag = 240;
+ encodedBytes = 4;
+ } else {
+ flag = 224;
+ encodedBytes = 3;
+ }
+ else {
+ flag = 192;
+ encodedBytes = 2;
+ }
+ t1 = 3 * encodedBytes;
+ codeUnits = new Uint8Array(t1);
+ for (index = 0; --encodedBytes, encodedBytes >= 0; flag = 128) {
+ byte = B.JSInt_methods._shrReceiverPositive$1(char, 6 * encodedBytes) & 63 | flag;
+ if (!(index < t1))
+ return A.ioore(codeUnits, index);
+ codeUnits[index] = 37;
+ t2 = index + 1;
+ t3 = byte >>> 4;
+ if (!(t3 < 16))
+ return A.ioore(_s16_, t3);
+ if (!(t2 < t1))
+ return A.ioore(codeUnits, t2);
+ codeUnits[t2] = _s16_.charCodeAt(t3);
+ t3 = index + 2;
+ if (!(t3 < t1))
+ return A.ioore(codeUnits, t3);
+ codeUnits[t3] = _s16_.charCodeAt(byte & 15);
+ index += 3;
+ }
+ }
+ return A.String_String$fromCharCodes(codeUnits, 0, null);
+ },
+ _Uri__normalizeOrSubstring(component, start, end, charTable, escapeDelimiters, replaceBackslash) {
+ var t1 = A._Uri__normalize(component, start, end, charTable, escapeDelimiters, replaceBackslash);
+ return t1 == null ? B.JSString_methods.substring$2(component, start, end) : t1;
+ },
+ _Uri__normalize(component, start, end, charTable, escapeDelimiters, replaceBackslash) {
+ var t1, t2, index, sectionStart, buffer, char, t3, replacement, sourceLength, tail, t4, _null = null;
+ for (t1 = !escapeDelimiters, t2 = component.length, index = start, sectionStart = index, buffer = _null; index < end;) {
+ if (!(index >= 0 && index < t2))
+ return A.ioore(component, index);
+ char = component.charCodeAt(index);
+ if (char < 127) {
+ t3 = char >>> 4;
+ if (!(t3 < 8))
+ return A.ioore(charTable, t3);
+ t3 = (charTable[t3] & 1 << (char & 15)) !== 0;
+ } else
+ t3 = false;
+ if (t3)
+ ++index;
+ else {
+ if (char === 37) {
+ replacement = A._Uri__normalizeEscape(component, index, false);
+ if (replacement == null) {
+ index += 3;
+ continue;
+ }
+ if ("%" === replacement) {
+ replacement = "%25";
+ sourceLength = 1;
+ } else
+ sourceLength = 3;
+ } else if (char === 92 && replaceBackslash) {
+ replacement = "/";
+ sourceLength = 1;
+ } else {
+ if (t1)
+ if (char <= 93) {
+ t3 = char >>> 4;
+ if (!(t3 < 8))
+ return A.ioore(B.List_YmH, t3);
+ t3 = (B.List_YmH[t3] & 1 << (char & 15)) !== 0;
+ } else
+ t3 = false;
+ else
+ t3 = false;
+ if (t3) {
+ A._Uri__fail(component, index, "Invalid character");
+ sourceLength = _null;
+ replacement = sourceLength;
+ } else {
+ if ((char & 64512) === 55296) {
+ t3 = index + 1;
+ if (t3 < end) {
+ if (!(t3 < t2))
+ return A.ioore(component, t3);
+ tail = component.charCodeAt(t3);
+ if ((tail & 64512) === 56320) {
+ char = (char & 1023) << 10 | tail & 1023 | 65536;
+ sourceLength = 2;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ } else
+ sourceLength = 1;
+ replacement = A._Uri__escapeChar(char);
+ }
+ }
+ if (buffer == null) {
+ buffer = new A.StringBuffer("");
+ t3 = buffer;
+ } else
+ t3 = buffer;
+ t4 = t3._contents += B.JSString_methods.substring$2(component, sectionStart, index);
+ t3._contents = t4 + A.S(replacement);
+ if (typeof sourceLength !== "number")
+ return A.iae(sourceLength);
+ index += sourceLength;
+ sectionStart = index;
+ }
+ }
+ if (buffer == null)
+ return _null;
+ if (sectionStart < end)
+ buffer._contents += B.JSString_methods.substring$2(component, sectionStart, end);
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__mayContainDotSegments(path) {
+ if (B.JSString_methods.startsWith$1(path, "."))
+ return true;
+ return B.JSString_methods.indexOf$1(path, "/.") !== -1;
+ },
+ _Uri__removeDotSegments(path) {
+ var output, t1, t2, appendSlash, _i, segment, t3;
+ if (!A._Uri__mayContainDotSegments(path))
+ return path;
+ output = A._setArrayType([], type$.JSArray_String);
+ for (t1 = path.split("/"), t2 = t1.length, appendSlash = false, _i = 0; _i < t2; ++_i) {
+ segment = t1[_i];
+ if (J.$eq$(segment, "..")) {
+ t3 = output.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return A.ioore(output, -1);
+ output.pop();
+ if (output.length === 0)
+ B.JSArray_methods.add$1(output, "");
+ }
+ appendSlash = true;
+ } else if ("." === segment)
+ appendSlash = true;
+ else {
+ B.JSArray_methods.add$1(output, segment);
+ appendSlash = false;
+ }
+ }
+ if (appendSlash)
+ B.JSArray_methods.add$1(output, "");
+ return B.JSArray_methods.join$1(output, "/");
+ },
+ _Uri__normalizeRelativePath(path, allowScheme) {
+ var output, t1, t2, appendSlash, _i, segment;
+ if (!A._Uri__mayContainDotSegments(path))
+ return !allowScheme ? A._Uri__escapeScheme(path) : path;
+ output = A._setArrayType([], type$.JSArray_String);
+ for (t1 = path.split("/"), t2 = t1.length, appendSlash = false, _i = 0; _i < t2; ++_i) {
+ segment = t1[_i];
+ if (".." === segment)
+ if (output.length !== 0 && B.JSArray_methods.get$last(output) !== "..") {
+ if (0 >= output.length)
+ return A.ioore(output, -1);
+ output.pop();
+ appendSlash = true;
+ } else {
+ B.JSArray_methods.add$1(output, "..");
+ appendSlash = false;
+ }
+ else if ("." === segment)
+ appendSlash = true;
+ else {
+ B.JSArray_methods.add$1(output, segment);
+ appendSlash = false;
+ }
+ }
+ t1 = output.length;
+ if (t1 !== 0)
+ if (t1 === 1) {
+ if (0 >= t1)
+ return A.ioore(output, 0);
+ t1 = output[0].length === 0;
+ } else
+ t1 = false;
+ else
+ t1 = true;
+ if (t1)
+ return "./";
+ if (appendSlash || B.JSArray_methods.get$last(output) === "..")
+ B.JSArray_methods.add$1(output, "");
+ if (!allowScheme) {
+ if (0 >= output.length)
+ return A.ioore(output, 0);
+ B.JSArray_methods.$indexSet(output, 0, A._Uri__escapeScheme(output[0]));
+ }
+ return B.JSArray_methods.join$1(output, "/");
+ },
+ _Uri__escapeScheme(path) {
+ var i, char, t2,
+ t1 = path.length;
+ if (t1 >= 2 && A._Uri__isAlphabeticCharacter(path.charCodeAt(0)))
+ for (i = 1; i < t1; ++i) {
+ char = path.charCodeAt(i);
+ if (char === 58)
+ return B.JSString_methods.substring$2(path, 0, i) + "%3A" + B.JSString_methods.substring$1(path, i + 1);
+ if (char <= 127) {
+ t2 = char >>> 4;
+ if (!(t2 < 8))
+ return A.ioore(B.List_MMm, t2);
+ t2 = (B.List_MMm[t2] & 1 << (char & 15)) === 0;
+ } else
+ t2 = true;
+ if (t2)
+ break;
+ }
+ return path;
+ },
+ _Uri__packageNameEnd(uri, path) {
+ if (uri.isScheme$1("package") && uri._host == null)
+ return A._skipPackageNameChars(path, 0, path.length);
+ return -1;
+ },
+ _Uri__toWindowsFilePath(uri) {
+ var hasDriveLetter, t2, host,
+ segments = uri.get$pathSegments(),
+ t1 = segments.length;
+ if (t1 > 0 && J.get$length$asx(segments[0]) === 2 && J.codeUnitAt$1$s(segments[0], 1) === 58) {
+ if (0 >= t1)
+ return A.ioore(segments, 0);
+ A._Uri__checkWindowsDriveLetter(J.codeUnitAt$1$s(segments[0], 0), false);
+ A._Uri__checkWindowsPathReservedCharacters(segments, false, 1);
+ hasDriveLetter = true;
+ } else {
+ A._Uri__checkWindowsPathReservedCharacters(segments, false, 0);
+ hasDriveLetter = false;
+ }
+ t2 = uri.get$hasAbsolutePath() && !hasDriveLetter ? "" + "\\" : "";
+ if (uri.get$hasAuthority()) {
+ host = uri.get$host(uri);
+ if (host.length !== 0)
+ t2 = t2 + "\\" + host + "\\";
+ }
+ t2 = A.StringBuffer__writeAll(t2, segments, "\\");
+ t1 = hasDriveLetter && t1 === 1 ? t2 + "\\" : t2;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ _Uri__hexCharPairToByte(s, pos) {
+ var t1, byte, i, t2, charCode;
+ for (t1 = s.length, byte = 0, i = 0; i < 2; ++i) {
+ t2 = pos + i;
+ if (!(t2 < t1))
+ return A.ioore(s, t2);
+ charCode = s.charCodeAt(t2);
+ if (48 <= charCode && charCode <= 57)
+ byte = byte * 16 + charCode - 48;
+ else {
+ charCode |= 32;
+ if (97 <= charCode && charCode <= 102)
+ byte = byte * 16 + charCode - 87;
+ else
+ throw A.wrapException(A.ArgumentError$("Invalid URL encoding", null));
+ }
+ }
+ return byte;
+ },
+ _Uri__uriDecode(text, start, end, encoding, plusToSpace) {
+ var simple, codeUnit, t2, bytes,
+ t1 = text.length,
+ i = start;
+ while (true) {
+ if (!(i < end)) {
+ simple = true;
+ break;
+ }
+ if (!(i < t1))
+ return A.ioore(text, i);
+ codeUnit = text.charCodeAt(i);
+ if (codeUnit <= 127)
+ if (codeUnit !== 37)
+ t2 = plusToSpace && codeUnit === 43;
+ else
+ t2 = true;
+ else
+ t2 = true;
+ if (t2) {
+ simple = false;
+ break;
+ }
+ ++i;
+ }
+ if (simple) {
+ if (B.C_Utf8Codec !== encoding)
+ t1 = false;
+ else
+ t1 = true;
+ if (t1)
+ return B.JSString_methods.substring$2(text, start, end);
+ else
+ bytes = new A.CodeUnits(B.JSString_methods.substring$2(text, start, end));
+ } else {
+ bytes = A._setArrayType([], type$.JSArray_int);
+ for (i = start; i < end; ++i) {
+ if (!(i < t1))
+ return A.ioore(text, i);
+ codeUnit = text.charCodeAt(i);
+ if (codeUnit > 127)
+ throw A.wrapException(A.ArgumentError$("Illegal percent encoding in URI", null));
+ if (codeUnit === 37) {
+ if (i + 3 > t1)
+ throw A.wrapException(A.ArgumentError$("Truncated URI", null));
+ B.JSArray_methods.add$1(bytes, A._Uri__hexCharPairToByte(text, i + 1));
+ i += 2;
+ } else if (plusToSpace && codeUnit === 43)
+ B.JSArray_methods.add$1(bytes, 32);
+ else
+ B.JSArray_methods.add$1(bytes, codeUnit);
+ }
+ }
+ type$.List_int._as(bytes);
+ return B.Utf8Decoder_false.convert$1(bytes);
+ },
+ _Uri__isAlphabeticCharacter(codeUnit) {
+ var lowerCase = codeUnit | 32;
+ return 97 <= lowerCase && lowerCase <= 122;
+ },
+ UriData__writeUri(mimeType, charsetName, parameters, buffer, indices) {
+ var slashIndex, t1;
+ if (true)
+ buffer._contents = buffer._contents;
+ else {
+ slashIndex = A.UriData__validateMimeType("");
+ if (slashIndex < 0)
+ throw A.wrapException(A.ArgumentError$value("", "mimeType", "Invalid MIME type"));
+ t1 = buffer._contents += A._Uri__uriEncode(B.List_yzX, B.JSString_methods.substring$2("", 0, slashIndex), B.C_Utf8Codec, false);
+ buffer._contents = t1 + "/";
+ buffer._contents += A._Uri__uriEncode(B.List_yzX, B.JSString_methods.substring$1("", slashIndex + 1), B.C_Utf8Codec, false);
+ }
+ },
+ UriData__validateMimeType(mimeType) {
+ var t1, slashIndex, i;
+ for (t1 = mimeType.length, slashIndex = -1, i = 0; i < t1; ++i) {
+ if (mimeType.charCodeAt(i) !== 47)
+ continue;
+ if (slashIndex < 0) {
+ slashIndex = i;
+ continue;
+ }
+ return -1;
+ }
+ return slashIndex;
+ },
+ UriData__parse(text, start, sourceUri) {
+ var t1, i, slashIndex, char, equalsIndex, lastSeparator, t2, data,
+ _s17_ = "Invalid MIME type",
+ indices = A._setArrayType([start - 1], type$.JSArray_int);
+ for (t1 = text.length, i = start, slashIndex = -1, char = null; i < t1; ++i) {
+ char = text.charCodeAt(i);
+ if (char === 44 || char === 59)
+ break;
+ if (char === 47) {
+ if (slashIndex < 0) {
+ slashIndex = i;
+ continue;
+ }
+ throw A.wrapException(A.FormatException$(_s17_, text, i));
+ }
+ }
+ if (slashIndex < 0 && i > start)
+ throw A.wrapException(A.FormatException$(_s17_, text, i));
+ for (; char !== 44;) {
+ B.JSArray_methods.add$1(indices, i);
+ ++i;
+ for (equalsIndex = -1; i < t1; ++i) {
+ if (!(i >= 0))
+ return A.ioore(text, i);
+ char = text.charCodeAt(i);
+ if (char === 61) {
+ if (equalsIndex < 0)
+ equalsIndex = i;
+ } else if (char === 59 || char === 44)
+ break;
+ }
+ if (equalsIndex >= 0)
+ B.JSArray_methods.add$1(indices, equalsIndex);
+ else {
+ lastSeparator = B.JSArray_methods.get$last(indices);
+ if (char !== 44 || i !== lastSeparator + 7 || !B.JSString_methods.startsWith$2(text, "base64", lastSeparator + 1))
+ throw A.wrapException(A.FormatException$("Expecting '='", text, i));
+ break;
+ }
+ }
+ B.JSArray_methods.add$1(indices, i);
+ t2 = i + 1;
+ if ((indices.length & 1) === 1)
+ text = B.C_Base64Codec.normalize$3(0, text, t2, t1);
+ else {
+ data = A._Uri__normalize(text, t2, t1, B.List_oFp, true, false);
+ if (data != null)
+ text = B.JSString_methods.replaceRange$3(text, t2, t1, data);
+ }
+ return new A.UriData(text, indices, sourceUri);
+ },
+ UriData__uriEncodeBytes(canonicalTable, bytes, buffer) {
+ var t1, byteOr, i, byte, t2,
+ _s16_ = "0123456789ABCDEF";
+ for (t1 = bytes.length, byteOr = 0, i = 0; i < t1; ++i) {
+ byte = bytes[i];
+ byteOr |= byte;
+ if (byte < 128) {
+ t2 = byte >>> 4;
+ if (!(t2 < 8))
+ return A.ioore(canonicalTable, t2);
+ t2 = (canonicalTable[t2] & 1 << (byte & 15)) !== 0;
+ } else
+ t2 = false;
+ if (t2)
+ buffer._contents += A.Primitives_stringFromCharCode(byte);
+ else {
+ buffer._contents += A.Primitives_stringFromCharCode(37);
+ t2 = byte >>> 4;
+ if (!(t2 < 16))
+ return A.ioore(_s16_, t2);
+ buffer._contents += A.Primitives_stringFromCharCode(_s16_.charCodeAt(t2));
+ buffer._contents += A.Primitives_stringFromCharCode(_s16_.charCodeAt(byte & 15));
+ }
+ }
+ if ((byteOr & 4294967040) !== 0)
+ for (i = 0; i < t1; ++i) {
+ byte = bytes[i];
+ if (byte > 255)
+ throw A.wrapException(A.ArgumentError$value(byte, "non-byte value", null));
+ }
+ },
+ _createTables() {
+ var _i, t1, t2, t3, b,
+ _s77_ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~!$&'()*+,;=",
+ _s1_ = ".", _s1_0 = ":", _s1_1 = "/", _s1_2 = "\\", _s1_3 = "?", _s1_4 = "#", _s2_ = "/\\",
+ tables = A._setArrayType(new Array(22), type$.JSArray_Uint8List);
+ for (_i = 0; _i < 22; ++_i)
+ tables[_i] = new Uint8Array(96);
+ t1 = new A._createTables_build(tables);
+ t2 = new A._createTables_setChars();
+ t3 = new A._createTables_setRange();
+ b = t1.call$2(0, 225);
+ t2.call$3(b, _s77_, 1);
+ t2.call$3(b, _s1_, 14);
+ t2.call$3(b, _s1_0, 34);
+ t2.call$3(b, _s1_1, 3);
+ t2.call$3(b, _s1_2, 227);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(14, 225);
+ t2.call$3(b, _s77_, 1);
+ t2.call$3(b, _s1_, 15);
+ t2.call$3(b, _s1_0, 34);
+ t2.call$3(b, _s2_, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(15, 225);
+ t2.call$3(b, _s77_, 1);
+ t2.call$3(b, "%", 225);
+ t2.call$3(b, _s1_0, 34);
+ t2.call$3(b, _s1_1, 9);
+ t2.call$3(b, _s1_2, 233);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(1, 225);
+ t2.call$3(b, _s77_, 1);
+ t2.call$3(b, _s1_0, 34);
+ t2.call$3(b, _s1_1, 10);
+ t2.call$3(b, _s1_2, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(2, 235);
+ t2.call$3(b, _s77_, 139);
+ t2.call$3(b, _s1_1, 131);
+ t2.call$3(b, _s1_2, 131);
+ t2.call$3(b, _s1_, 146);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(3, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s1_1, 68);
+ t2.call$3(b, _s1_2, 68);
+ t2.call$3(b, _s1_, 18);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(4, 229);
+ t2.call$3(b, _s77_, 5);
+ t3.call$3(b, "AZ", 229);
+ t2.call$3(b, _s1_0, 102);
+ t2.call$3(b, "@", 68);
+ t2.call$3(b, "[", 232);
+ t2.call$3(b, _s1_1, 138);
+ t2.call$3(b, _s1_2, 138);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(5, 229);
+ t2.call$3(b, _s77_, 5);
+ t3.call$3(b, "AZ", 229);
+ t2.call$3(b, _s1_0, 102);
+ t2.call$3(b, "@", 68);
+ t2.call$3(b, _s1_1, 138);
+ t2.call$3(b, _s1_2, 138);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(6, 231);
+ t3.call$3(b, "19", 7);
+ t2.call$3(b, "@", 68);
+ t2.call$3(b, _s1_1, 138);
+ t2.call$3(b, _s1_2, 138);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(7, 231);
+ t3.call$3(b, "09", 7);
+ t2.call$3(b, "@", 68);
+ t2.call$3(b, _s1_1, 138);
+ t2.call$3(b, _s1_2, 138);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ t2.call$3(t1.call$2(8, 8), "]", 5);
+ b = t1.call$2(9, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s1_, 16);
+ t2.call$3(b, _s2_, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(16, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s1_, 17);
+ t2.call$3(b, _s2_, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(17, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s1_1, 9);
+ t2.call$3(b, _s1_2, 233);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(10, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s1_, 18);
+ t2.call$3(b, _s1_1, 10);
+ t2.call$3(b, _s1_2, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(18, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s1_, 19);
+ t2.call$3(b, _s2_, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(19, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s2_, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(11, 235);
+ t2.call$3(b, _s77_, 11);
+ t2.call$3(b, _s1_1, 10);
+ t2.call$3(b, _s1_2, 234);
+ t2.call$3(b, _s1_3, 172);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(12, 236);
+ t2.call$3(b, _s77_, 12);
+ t2.call$3(b, _s1_3, 12);
+ t2.call$3(b, _s1_4, 205);
+ b = t1.call$2(13, 237);
+ t2.call$3(b, _s77_, 13);
+ t2.call$3(b, _s1_3, 13);
+ t3.call$3(t1.call$2(20, 245), "az", 21);
+ b = t1.call$2(21, 245);
+ t3.call$3(b, "az", 21);
+ t3.call$3(b, "09", 21);
+ t2.call$3(b, "+-.", 21);
+ return tables;
+ },
+ _scan(uri, start, end, state, indices) {
+ var t1, i, table, char, transition,
+ tables = $.$get$_scannerTables();
+ for (t1 = uri.length, i = start; i < end; ++i) {
+ if (!(state >= 0 && state < tables.length))
+ return A.ioore(tables, state);
+ table = tables[state];
+ if (!(i < t1))
+ return A.ioore(uri, i);
+ char = uri.charCodeAt(i) ^ 96;
+ transition = table[char > 95 ? 31 : char];
+ state = transition & 31;
+ B.JSArray_methods.$indexSet(indices, transition >>> 5, i);
+ }
+ return state;
+ },
+ _SimpleUri__packageNameEnd(uri) {
+ if (uri._schemeEnd === 7 && B.JSString_methods.startsWith$1(uri._uri, "package") && uri._hostStart <= 0)
+ return A._skipPackageNameChars(uri._uri, uri._pathStart, uri._queryStart);
+ return -1;
+ },
+ _skipPackageNameChars(source, start, end) {
+ var t1, i, dots, char;
+ for (t1 = source.length, i = start, dots = 0; i < end; ++i) {
+ if (!(i >= 0 && i < t1))
+ return A.ioore(source, i);
+ char = source.charCodeAt(i);
+ if (char === 47)
+ return dots !== 0 ? i : -1;
+ if (char === 37 || char === 58)
+ return -1;
+ dots |= char ^ 46;
+ }
+ return -1;
+ },
+ _caseInsensitiveCompareStart(prefix, string, start) {
+ var t1, t2, result, i, t3, stringChar, delta, lowerChar;
+ for (t1 = prefix.length, t2 = string.length, result = 0, i = 0; i < t1; ++i) {
+ t3 = start + i;
+ if (!(t3 < t2))
+ return A.ioore(string, t3);
+ stringChar = string.charCodeAt(t3);
+ delta = prefix.charCodeAt(i) ^ stringChar;
+ if (delta !== 0) {
+ if (delta === 32) {
+ lowerChar = stringChar | delta;
+ if (97 <= lowerChar && lowerChar <= 122) {
+ result = 32;
+ continue;
+ }
+ }
+ return -1;
+ }
+ }
+ return result;
+ },
+ NoSuchMethodError_toString_closure: function NoSuchMethodError_toString_closure(t0, t1) {
+ this._box_0 = t0;
+ this.sb = t1;
+ },
+ DateTime: function DateTime(t0, t1) {
+ this._core$_value = t0;
+ this.isUtc = t1;
+ },
+ Duration: function Duration(t0) {
+ this._duration = t0;
+ },
+ Error: function Error() {
+ },
+ AssertionError: function AssertionError(t0) {
+ this.message = t0;
+ },
+ TypeError: function TypeError() {
+ },
+ ArgumentError: function ArgumentError(t0, t1, t2, t3) {
+ var _ = this;
+ _._hasValue = t0;
+ _.invalidValue = t1;
+ _.name = t2;
+ _.message = t3;
+ },
+ RangeError: function RangeError(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _.start = t0;
+ _.end = t1;
+ _._hasValue = t2;
+ _.invalidValue = t3;
+ _.name = t4;
+ _.message = t5;
+ },
+ IndexError: function IndexError(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _.length = t0;
+ _._hasValue = t1;
+ _.invalidValue = t2;
+ _.name = t3;
+ _.message = t4;
+ },
+ NoSuchMethodError: function NoSuchMethodError(t0, t1, t2, t3) {
+ var _ = this;
+ _._core$_receiver = t0;
+ _._core$_memberName = t1;
+ _._core$_arguments = t2;
+ _._namedArguments = t3;
+ },
+ UnsupportedError: function UnsupportedError(t0) {
+ this.message = t0;
+ },
+ UnimplementedError: function UnimplementedError(t0) {
+ this.message = t0;
+ },
+ StateError: function StateError(t0) {
+ this.message = t0;
+ },
+ ConcurrentModificationError: function ConcurrentModificationError(t0) {
+ this.modifiedObject = t0;
+ },
+ OutOfMemoryError: function OutOfMemoryError() {
+ },
+ StackOverflowError: function StackOverflowError() {
+ },
+ _Exception: function _Exception(t0) {
+ this.message = t0;
+ },
+ FormatException: function FormatException(t0, t1, t2) {
+ this.message = t0;
+ this.source = t1;
+ this.offset = t2;
+ },
+ Iterable: function Iterable() {
+ },
+ Null: function Null() {
+ },
+ Object: function Object() {
+ },
+ _StringStackTrace: function _StringStackTrace(t0) {
+ this._stackTrace = t0;
+ },
+ StringBuffer: function StringBuffer(t0) {
+ this._contents = t0;
+ },
+ Uri_splitQueryString_closure: function Uri_splitQueryString_closure(t0) {
+ this.encoding = t0;
+ },
+ Uri__parseIPv4Address_error: function Uri__parseIPv4Address_error(t0) {
+ this.host = t0;
+ },
+ Uri_parseIPv6Address_error: function Uri_parseIPv6Address_error(t0) {
+ this.host = t0;
+ },
+ Uri_parseIPv6Address_parseHex: function Uri_parseIPv6Address_parseHex(t0, t1) {
+ this.error = t0;
+ this.host = t1;
+ },
+ _Uri: function _Uri(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _.scheme = t0;
+ _._userInfo = t1;
+ _._host = t2;
+ _._port = t3;
+ _.path = t4;
+ _._query = t5;
+ _._fragment = t6;
+ _.___Uri_queryParameters_FI = _.___Uri_hashCode_FI = _.___Uri_pathSegments_FI = _.___Uri__text_FI = $;
+ },
+ _Uri__makePath_closure: function _Uri__makePath_closure() {
+ },
+ UriData: function UriData(t0, t1, t2) {
+ this._text = t0;
+ this._separatorIndices = t1;
+ this._uriCache = t2;
+ },
+ _createTables_build: function _createTables_build(t0) {
+ this.tables = t0;
+ },
+ _createTables_setChars: function _createTables_setChars() {
+ },
+ _createTables_setRange: function _createTables_setRange() {
+ },
+ _SimpleUri: function _SimpleUri(t0, t1, t2, t3, t4, t5, t6, t7) {
+ var _ = this;
+ _._uri = t0;
+ _._schemeEnd = t1;
+ _._hostStart = t2;
+ _._portStart = t3;
+ _._pathStart = t4;
+ _._queryStart = t5;
+ _._fragmentStart = t6;
+ _._schemeCache = t7;
+ _._hashCodeCache = null;
+ },
+ _DataUri: function _DataUri(t0, t1, t2, t3, t4, t5, t6) {
+ var _ = this;
+ _.scheme = t0;
+ _._userInfo = t1;
+ _._host = t2;
+ _._port = t3;
+ _.path = t4;
+ _._query = t5;
+ _._fragment = t6;
+ _.___Uri_queryParameters_FI = _.___Uri_hashCode_FI = _.___Uri_pathSegments_FI = _.___Uri__text_FI = $;
+ },
+ HtmlElement: function HtmlElement() {
+ },
+ AccessibleNodeList: function AccessibleNodeList() {
+ },
+ AnchorElement: function AnchorElement() {
+ },
+ AreaElement: function AreaElement() {
+ },
+ Blob: function Blob() {
+ },
+ CharacterData: function CharacterData() {
+ },
+ CssPerspective: function CssPerspective() {
+ },
+ CssRule: function CssRule() {
+ },
+ CssStyleDeclaration: function CssStyleDeclaration() {
+ },
+ CssStyleDeclarationBase: function CssStyleDeclarationBase() {
+ },
+ CssStyleValue: function CssStyleValue() {
+ },
+ CssTransformComponent: function CssTransformComponent() {
+ },
+ CssTransformValue: function CssTransformValue() {
+ },
+ CssUnparsedValue: function CssUnparsedValue() {
+ },
+ DataTransferItemList: function DataTransferItemList() {
+ },
+ DomException: function DomException() {
+ },
+ DomRectList: function DomRectList() {
+ },
+ DomRectReadOnly: function DomRectReadOnly() {
+ },
+ DomStringList: function DomStringList() {
+ },
+ DomTokenList: function DomTokenList() {
+ },
+ Element: function Element() {
+ },
+ EventTarget: function EventTarget() {
+ },
+ File: function File() {
+ },
+ FileList: function FileList() {
+ },
+ FileWriter: function FileWriter() {
+ },
+ FormElement: function FormElement() {
+ },
+ Gamepad: function Gamepad() {
+ },
+ History: function History() {
+ },
+ HtmlCollection: function HtmlCollection() {
+ },
+ Location: function Location() {
+ },
+ MediaList: function MediaList() {
+ },
+ MidiInputMap: function MidiInputMap() {
+ },
+ MidiInputMap_keys_closure: function MidiInputMap_keys_closure(t0) {
+ this.keys = t0;
+ },
+ MidiOutputMap: function MidiOutputMap() {
+ },
+ MidiOutputMap_keys_closure: function MidiOutputMap_keys_closure(t0) {
+ this.keys = t0;
+ },
+ MimeType: function MimeType() {
+ },
+ MimeTypeArray: function MimeTypeArray() {
+ },
+ Node: function Node() {
+ },
+ NodeList: function NodeList() {
+ },
+ Plugin: function Plugin() {
+ },
+ PluginArray: function PluginArray() {
+ },
+ RtcStatsReport: function RtcStatsReport() {
+ },
+ RtcStatsReport_keys_closure: function RtcStatsReport_keys_closure(t0) {
+ this.keys = t0;
+ },
+ SelectElement: function SelectElement() {
+ },
+ SourceBuffer: function SourceBuffer() {
+ },
+ SourceBufferList: function SourceBufferList() {
+ },
+ SpeechGrammar: function SpeechGrammar() {
+ },
+ SpeechGrammarList: function SpeechGrammarList() {
+ },
+ SpeechRecognitionResult: function SpeechRecognitionResult() {
+ },
+ Storage: function Storage() {
+ },
+ Storage_keys_closure: function Storage_keys_closure(t0) {
+ this.keys = t0;
+ },
+ StyleSheet: function StyleSheet() {
+ },
+ TextTrack: function TextTrack() {
+ },
+ TextTrackCue: function TextTrackCue() {
+ },
+ TextTrackCueList: function TextTrackCueList() {
+ },
+ TextTrackList: function TextTrackList() {
+ },
+ TimeRanges: function TimeRanges() {
+ },
+ Touch: function Touch() {
+ },
+ TouchList: function TouchList() {
+ },
+ TrackDefaultList: function TrackDefaultList() {
+ },
+ Url: function Url() {
+ },
+ VideoTrackList: function VideoTrackList() {
+ },
+ _CssRuleList: function _CssRuleList() {
+ },
+ _DomRect: function _DomRect() {
+ },
+ _GamepadList: function _GamepadList() {
+ },
+ _NamedNodeMap: function _NamedNodeMap() {
+ },
+ _SpeechRecognitionResultList: function _SpeechRecognitionResultList() {
+ },
+ _StyleSheetList: function _StyleSheetList() {
+ },
+ ImmutableListMixin: function ImmutableListMixin() {
+ },
+ FixedSizeListIterator: function FixedSizeListIterator(t0, t1, t2) {
+ var _ = this;
+ _._array = t0;
+ _._html$_length = t1;
+ _._position = -1;
+ _._html$_current = null;
+ _.$ti = t2;
+ },
+ _CssStyleDeclaration_JavaScriptObject_CssStyleDeclarationBase: function _CssStyleDeclaration_JavaScriptObject_CssStyleDeclarationBase() {
+ },
+ _DomRectList_JavaScriptObject_ListMixin: function _DomRectList_JavaScriptObject_ListMixin() {
+ },
+ _DomRectList_JavaScriptObject_ListMixin_ImmutableListMixin: function _DomRectList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _DomStringList_JavaScriptObject_ListMixin: function _DomStringList_JavaScriptObject_ListMixin() {
+ },
+ _DomStringList_JavaScriptObject_ListMixin_ImmutableListMixin: function _DomStringList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _FileList_JavaScriptObject_ListMixin: function _FileList_JavaScriptObject_ListMixin() {
+ },
+ _FileList_JavaScriptObject_ListMixin_ImmutableListMixin: function _FileList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _HtmlCollection_JavaScriptObject_ListMixin: function _HtmlCollection_JavaScriptObject_ListMixin() {
+ },
+ _HtmlCollection_JavaScriptObject_ListMixin_ImmutableListMixin: function _HtmlCollection_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _MidiInputMap_JavaScriptObject_MapMixin: function _MidiInputMap_JavaScriptObject_MapMixin() {
+ },
+ _MidiOutputMap_JavaScriptObject_MapMixin: function _MidiOutputMap_JavaScriptObject_MapMixin() {
+ },
+ _MimeTypeArray_JavaScriptObject_ListMixin: function _MimeTypeArray_JavaScriptObject_ListMixin() {
+ },
+ _MimeTypeArray_JavaScriptObject_ListMixin_ImmutableListMixin: function _MimeTypeArray_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _NodeList_JavaScriptObject_ListMixin: function _NodeList_JavaScriptObject_ListMixin() {
+ },
+ _NodeList_JavaScriptObject_ListMixin_ImmutableListMixin: function _NodeList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _PluginArray_JavaScriptObject_ListMixin: function _PluginArray_JavaScriptObject_ListMixin() {
+ },
+ _PluginArray_JavaScriptObject_ListMixin_ImmutableListMixin: function _PluginArray_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _RtcStatsReport_JavaScriptObject_MapMixin: function _RtcStatsReport_JavaScriptObject_MapMixin() {
+ },
+ _SourceBufferList_EventTarget_ListMixin: function _SourceBufferList_EventTarget_ListMixin() {
+ },
+ _SourceBufferList_EventTarget_ListMixin_ImmutableListMixin: function _SourceBufferList_EventTarget_ListMixin_ImmutableListMixin() {
+ },
+ _SpeechGrammarList_JavaScriptObject_ListMixin: function _SpeechGrammarList_JavaScriptObject_ListMixin() {
+ },
+ _SpeechGrammarList_JavaScriptObject_ListMixin_ImmutableListMixin: function _SpeechGrammarList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _Storage_JavaScriptObject_MapMixin: function _Storage_JavaScriptObject_MapMixin() {
+ },
+ _TextTrackCueList_JavaScriptObject_ListMixin: function _TextTrackCueList_JavaScriptObject_ListMixin() {
+ },
+ _TextTrackCueList_JavaScriptObject_ListMixin_ImmutableListMixin: function _TextTrackCueList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _TextTrackList_EventTarget_ListMixin: function _TextTrackList_EventTarget_ListMixin() {
+ },
+ _TextTrackList_EventTarget_ListMixin_ImmutableListMixin: function _TextTrackList_EventTarget_ListMixin_ImmutableListMixin() {
+ },
+ _TouchList_JavaScriptObject_ListMixin: function _TouchList_JavaScriptObject_ListMixin() {
+ },
+ _TouchList_JavaScriptObject_ListMixin_ImmutableListMixin: function _TouchList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ __CssRuleList_JavaScriptObject_ListMixin: function __CssRuleList_JavaScriptObject_ListMixin() {
+ },
+ __CssRuleList_JavaScriptObject_ListMixin_ImmutableListMixin: function __CssRuleList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ __GamepadList_JavaScriptObject_ListMixin: function __GamepadList_JavaScriptObject_ListMixin() {
+ },
+ __GamepadList_JavaScriptObject_ListMixin_ImmutableListMixin: function __GamepadList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ __NamedNodeMap_JavaScriptObject_ListMixin: function __NamedNodeMap_JavaScriptObject_ListMixin() {
+ },
+ __NamedNodeMap_JavaScriptObject_ListMixin_ImmutableListMixin: function __NamedNodeMap_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ __SpeechRecognitionResultList_JavaScriptObject_ListMixin: function __SpeechRecognitionResultList_JavaScriptObject_ListMixin() {
+ },
+ __SpeechRecognitionResultList_JavaScriptObject_ListMixin_ImmutableListMixin: function __SpeechRecognitionResultList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ __StyleSheetList_JavaScriptObject_ListMixin: function __StyleSheetList_JavaScriptObject_ListMixin() {
+ },
+ __StyleSheetList_JavaScriptObject_ListMixin_ImmutableListMixin: function __StyleSheetList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _convertDartFunctionFast(f) {
+ var ret,
+ existing = f.$dart_jsFunction;
+ if (existing != null)
+ return existing;
+ ret = function(_call, f) {
+ return function() {
+ return _call(f, Array.prototype.slice.apply(arguments));
+ };
+ }(A._callDartFunctionFast, f);
+ ret[$.$get$DART_CLOSURE_PROPERTY_NAME()] = f;
+ f.$dart_jsFunction = ret;
+ return ret;
+ },
+ _callDartFunctionFast(callback, $arguments) {
+ type$.List_dynamic._as($arguments);
+ type$.Function._as(callback);
+ return A.Primitives_applyFunction(callback, $arguments, null);
+ },
+ allowInterop(f, $F) {
+ if (typeof f == "function")
+ return f;
+ else
+ return $F._as(A._convertDartFunctionFast(f));
+ },
+ _noJsifyRequired(o) {
+ return o == null || A._isBool(o) || typeof o == "number" || typeof o == "string" || type$.Int8List._is(o) || type$.Uint8List._is(o) || type$.Uint8ClampedList._is(o) || type$.Int16List._is(o) || type$.Uint16List._is(o) || type$.Int32List._is(o) || type$.Uint32List._is(o) || type$.Float32List._is(o) || type$.Float64List._is(o) || type$.ByteBuffer._is(o) || type$.ByteData._is(o);
+ },
+ jsify(object) {
+ if (A._noJsifyRequired(object))
+ return object;
+ return new A.jsify__convert(new A._IdentityHashMap(type$._IdentityHashMap_of_nullable_Object_and_nullable_Object)).call$1(object);
+ },
+ getProperty(o, $name, $T) {
+ return $T._as(o[$name]);
+ },
+ callMethod(o, method, args, $T) {
+ return $T._as(o[method].apply(o, args));
+ },
+ callConstructor(constr, $arguments, $T) {
+ var args, factoryFunction;
+ if ($arguments instanceof Array)
+ switch ($arguments.length) {
+ case 0:
+ return $T._as(new constr());
+ case 1:
+ return $T._as(new constr($arguments[0]));
+ case 2:
+ return $T._as(new constr($arguments[0], $arguments[1]));
+ case 3:
+ return $T._as(new constr($arguments[0], $arguments[1], $arguments[2]));
+ case 4:
+ return $T._as(new constr($arguments[0], $arguments[1], $arguments[2], $arguments[3]));
+ }
+ args = [null];
+ B.JSArray_methods.addAll$1(args, $arguments);
+ factoryFunction = constr.bind.apply(constr, args);
+ String(factoryFunction);
+ return $T._as(new factoryFunction());
+ },
+ promiseToFuture(jsPromise, $T) {
+ var t1 = new A._Future($.Zone__current, $T._eval$1("_Future<0>")),
+ completer = new A._AsyncCompleter(t1, $T._eval$1("_AsyncCompleter<0>"));
+ jsPromise.then(A.convertDartClosureToJS(new A.promiseToFuture_closure(completer, $T), 1), A.convertDartClosureToJS(new A.promiseToFuture_closure0(completer), 1));
+ return t1;
+ },
+ _noDartifyRequired(o) {
+ return o == null || typeof o === "boolean" || typeof o === "number" || typeof o === "string" || o instanceof Int8Array || o instanceof Uint8Array || o instanceof Uint8ClampedArray || o instanceof Int16Array || o instanceof Uint16Array || o instanceof Int32Array || o instanceof Uint32Array || o instanceof Float32Array || o instanceof Float64Array || o instanceof ArrayBuffer || o instanceof DataView;
+ },
+ dartify(o) {
+ if (A._noDartifyRequired(o))
+ return o;
+ return new A.dartify_convert(new A._IdentityHashMap(type$._IdentityHashMap_of_nullable_Object_and_nullable_Object)).call$1(o);
+ },
+ jsify__convert: function jsify__convert(t0) {
+ this._convertedObjects = t0;
+ },
+ promiseToFuture_closure: function promiseToFuture_closure(t0, t1) {
+ this.completer = t0;
+ this.T = t1;
+ },
+ promiseToFuture_closure0: function promiseToFuture_closure0(t0) {
+ this.completer = t0;
+ },
+ dartify_convert: function dartify_convert(t0) {
+ this._convertedObjects = t0;
+ },
+ NullRejectionException: function NullRejectionException(t0) {
+ this.isUndefined = t0;
+ },
+ Length: function Length() {
+ },
+ LengthList: function LengthList() {
+ },
+ Number: function Number() {
+ },
+ NumberList: function NumberList() {
+ },
+ PointList: function PointList() {
+ },
+ StringList: function StringList() {
+ },
+ Transform: function Transform() {
+ },
+ TransformList: function TransformList() {
+ },
+ _LengthList_JavaScriptObject_ListMixin: function _LengthList_JavaScriptObject_ListMixin() {
+ },
+ _LengthList_JavaScriptObject_ListMixin_ImmutableListMixin: function _LengthList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _NumberList_JavaScriptObject_ListMixin: function _NumberList_JavaScriptObject_ListMixin() {
+ },
+ _NumberList_JavaScriptObject_ListMixin_ImmutableListMixin: function _NumberList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _StringList_JavaScriptObject_ListMixin: function _StringList_JavaScriptObject_ListMixin() {
+ },
+ _StringList_JavaScriptObject_ListMixin_ImmutableListMixin: function _StringList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ _TransformList_JavaScriptObject_ListMixin: function _TransformList_JavaScriptObject_ListMixin() {
+ },
+ _TransformList_JavaScriptObject_ListMixin_ImmutableListMixin: function _TransformList_JavaScriptObject_ListMixin_ImmutableListMixin() {
+ },
+ AudioBuffer: function AudioBuffer() {
+ },
+ AudioParamMap: function AudioParamMap() {
+ },
+ AudioParamMap_keys_closure: function AudioParamMap_keys_closure(t0) {
+ this.keys = t0;
+ },
+ AudioTrackList: function AudioTrackList() {
+ },
+ BaseAudioContext: function BaseAudioContext() {
+ },
+ OfflineAudioContext: function OfflineAudioContext() {
+ },
+ _AudioParamMap_JavaScriptObject_MapMixin: function _AudioParamMap_JavaScriptObject_MapMixin() {
+ },
+ NullStreamSink: function NullStreamSink(t0, t1) {
+ var _ = this;
+ _.done = t0;
+ _._addingStream = _._null_stream_sink$_closed = false;
+ _.$ti = t1;
+ },
+ NullStreamSink_addStream_closure: function NullStreamSink_addStream_closure(t0) {
+ this.$this = t0;
+ },
+ Context_Context(style) {
+ return new A.Context(style, ".");
+ },
+ _parseUri(uri) {
+ return uri;
+ },
+ _validateArgList(method, args) {
+ var numArgs, i, numArgs0, message, t1, t2, t3, t4;
+ for (numArgs = args.length, i = 1; i < numArgs; ++i) {
+ if (args[i] == null || args[i - 1] != null)
+ continue;
+ for (; numArgs >= 1; numArgs = numArgs0) {
+ numArgs0 = numArgs - 1;
+ if (args[numArgs0] != null)
+ break;
+ }
+ message = new A.StringBuffer("");
+ t1 = "" + (method + "(");
+ message._contents = t1;
+ t2 = A._arrayInstanceType(args);
+ t3 = t2._eval$1("SubListIterable<1>");
+ t4 = new A.SubListIterable(args, 0, numArgs, t3);
+ t4.SubListIterable$3(args, 0, numArgs, t2._precomputed1);
+ t3 = t1 + new A.MappedListIterable(t4, t3._eval$1("String(ListIterable.E)")._as(new A._validateArgList_closure()), t3._eval$1("MappedListIterable<ListIterable.E,String>")).join$1(0, ", ");
+ message._contents = t3;
+ message._contents = t3 + ("): part " + (i - 1) + " was null, but part " + i + " was not.");
+ throw A.wrapException(A.ArgumentError$(message.toString$0(0), null));
+ }
+ },
+ Context: function Context(t0, t1) {
+ this.style = t0;
+ this._context$_current = t1;
+ },
+ Context_joinAll_closure: function Context_joinAll_closure() {
+ },
+ Context_split_closure: function Context_split_closure() {
+ },
+ _validateArgList_closure: function _validateArgList_closure() {
+ },
+ InternalStyle: function InternalStyle() {
+ },
+ ParsedPath_ParsedPath$parse(path, style) {
+ var t1, parts, separators, t2, start, i,
+ root = style.getRoot$1(path);
+ style.isRootRelative$1(path);
+ if (root != null)
+ path = B.JSString_methods.substring$1(path, root.length);
+ t1 = type$.JSArray_String;
+ parts = A._setArrayType([], t1);
+ separators = A._setArrayType([], t1);
+ t1 = path.length;
+ if (t1 !== 0) {
+ if (0 >= t1)
+ return A.ioore(path, 0);
+ t2 = style.isSeparator$1(path.charCodeAt(0));
+ } else
+ t2 = false;
+ if (t2) {
+ if (0 >= t1)
+ return A.ioore(path, 0);
+ B.JSArray_methods.add$1(separators, path[0]);
+ start = 1;
+ } else {
+ B.JSArray_methods.add$1(separators, "");
+ start = 0;
+ }
+ for (i = start; i < t1; ++i)
+ if (style.isSeparator$1(path.charCodeAt(i))) {
+ B.JSArray_methods.add$1(parts, B.JSString_methods.substring$2(path, start, i));
+ B.JSArray_methods.add$1(separators, path[i]);
+ start = i + 1;
+ }
+ if (start < t1) {
+ B.JSArray_methods.add$1(parts, B.JSString_methods.substring$1(path, start));
+ B.JSArray_methods.add$1(separators, "");
+ }
+ return new A.ParsedPath(style, root, parts, separators);
+ },
+ ParsedPath: function ParsedPath(t0, t1, t2, t3) {
+ var _ = this;
+ _.style = t0;
+ _.root = t1;
+ _.parts = t2;
+ _.separators = t3;
+ },
+ PathException$(message) {
+ return new A.PathException(message);
+ },
+ PathException: function PathException(t0) {
+ this.message = t0;
+ },
+ Style__getPlatformStyle() {
+ if (A.Uri_base().get$scheme() !== "file")
+ return $.$get$Style_url();
+ var t1 = A.Uri_base();
+ if (!B.JSString_methods.endsWith$1(t1.get$path(t1), "/"))
+ return $.$get$Style_url();
+ if (A._Uri__Uri(null, "a/b", null, null).toFilePath$0() === "a\\b")
+ return $.$get$Style_windows();
+ return $.$get$Style_posix();
+ },
+ Style: function Style() {
+ },
+ PosixStyle: function PosixStyle(t0, t1, t2) {
+ this.separatorPattern = t0;
+ this.needsSeparatorPattern = t1;
+ this.rootPattern = t2;
+ },
+ UrlStyle: function UrlStyle(t0, t1, t2, t3) {
+ var _ = this;
+ _.separatorPattern = t0;
+ _.needsSeparatorPattern = t1;
+ _.rootPattern = t2;
+ _.relativeRootPattern = t3;
+ },
+ WindowsStyle: function WindowsStyle(t0, t1, t2, t3) {
+ var _ = this;
+ _.separatorPattern = t0;
+ _.needsSeparatorPattern = t1;
+ _.rootPattern = t2;
+ _.relativeRootPattern = t3;
+ },
+ WindowsStyle_absolutePathToUri_closure: function WindowsStyle_absolutePathToUri_closure() {
+ },
+ Chain_Chain$parse(chain) {
+ var t1, t2,
+ _s51_ = string$.x3d_____;
+ if (chain.length === 0)
+ return new A.Chain(A.List_List$unmodifiable(A._setArrayType([], type$.JSArray_Trace), type$.Trace));
+ t1 = $.$get$vmChainGap();
+ if (B.JSString_methods.contains$1(chain, t1)) {
+ t1 = B.JSString_methods.split$1(chain, t1);
+ t2 = A._arrayInstanceType(t1);
+ return new A.Chain(A.List_List$unmodifiable(new A.MappedIterable(new A.WhereIterable(t1, t2._eval$1("bool(1)")._as(new A.Chain_Chain$parse_closure()), t2._eval$1("WhereIterable<1>")), t2._eval$1("Trace(1)")._as(A.trace_Trace___parseVM_tearOff$closure()), t2._eval$1("MappedIterable<1,Trace>")), type$.Trace));
+ }
+ if (!B.JSString_methods.contains$1(chain, _s51_))
+ return new A.Chain(A.List_List$unmodifiable(A._setArrayType([A.Trace_Trace$parse(chain)], type$.JSArray_Trace), type$.Trace));
+ return new A.Chain(A.List_List$unmodifiable(new A.MappedListIterable(A._setArrayType(chain.split(_s51_), type$.JSArray_String), type$.Trace_Function_String._as(A.trace_Trace___parseFriendly_tearOff$closure()), type$.MappedListIterable_String_Trace), type$.Trace));
+ },
+ Chain: function Chain(t0) {
+ this.traces = t0;
+ },
+ Chain_Chain$parse_closure: function Chain_Chain$parse_closure() {
+ },
+ Chain_toTrace_closure: function Chain_toTrace_closure() {
+ },
+ Chain_toString_closure0: function Chain_toString_closure0() {
+ },
+ Chain_toString__closure0: function Chain_toString__closure0() {
+ },
+ Chain_toString_closure: function Chain_toString_closure(t0) {
+ this.longest = t0;
+ },
+ Chain_toString__closure: function Chain_toString__closure(t0) {
+ this.longest = t0;
+ },
+ Frame___parseVM_tearOff(frame) {
+ return A.Frame_Frame$parseVM(A._asString(frame));
+ },
+ Frame_Frame$parseVM(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseVM_closure(frame));
+ },
+ Frame___parseV8_tearOff(frame) {
+ return A.Frame_Frame$parseV8(A._asString(frame));
+ },
+ Frame_Frame$parseV8(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseV8_closure(frame));
+ },
+ Frame_Frame$_parseFirefoxEval(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$_parseFirefoxEval_closure(frame));
+ },
+ Frame___parseFirefox_tearOff(frame) {
+ return A.Frame_Frame$parseFirefox(A._asString(frame));
+ },
+ Frame_Frame$parseFirefox(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseFirefox_closure(frame));
+ },
+ Frame___parseFriendly_tearOff(frame) {
+ return A.Frame_Frame$parseFriendly(A._asString(frame));
+ },
+ Frame_Frame$parseFriendly(frame) {
+ return A.Frame__catchFormatException(frame, new A.Frame_Frame$parseFriendly_closure(frame));
+ },
+ Frame__uriOrPathToUri(uriOrPath) {
+ if (B.JSString_methods.contains$1(uriOrPath, $.$get$Frame__uriRegExp()))
+ return A.Uri_parse(uriOrPath);
+ else if (B.JSString_methods.contains$1(uriOrPath, $.$get$Frame__windowsRegExp()))
+ return A._Uri__Uri$file(uriOrPath, true);
+ else if (B.JSString_methods.startsWith$1(uriOrPath, "/"))
+ return A._Uri__Uri$file(uriOrPath, false);
+ if (B.JSString_methods.contains$1(uriOrPath, "\\"))
+ return $.$get$windows().toUri$1(uriOrPath);
+ return A.Uri_parse(uriOrPath);
+ },
+ Frame__catchFormatException(text, body) {
+ var t1, exception;
+ try {
+ t1 = body.call$0();
+ return t1;
+ } catch (exception) {
+ if (A.unwrapException(exception) instanceof A.FormatException)
+ return new A.UnparsedFrame(A._Uri__Uri(null, "unparsed", null, null), text);
+ else
+ throw exception;
+ }
+ },
+ Frame: function Frame(t0, t1, t2, t3) {
+ var _ = this;
+ _.uri = t0;
+ _.line = t1;
+ _.column = t2;
+ _.member = t3;
+ },
+ Frame_Frame$parseVM_closure: function Frame_Frame$parseVM_closure(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseV8_closure: function Frame_Frame$parseV8_closure(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseV8_closure_parseLocation: function Frame_Frame$parseV8_closure_parseLocation(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$_parseFirefoxEval_closure: function Frame_Frame$_parseFirefoxEval_closure(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseFirefox_closure: function Frame_Frame$parseFirefox_closure(t0) {
+ this.frame = t0;
+ },
+ Frame_Frame$parseFriendly_closure: function Frame_Frame$parseFriendly_closure(t0) {
+ this.frame = t0;
+ },
+ LazyTrace: function LazyTrace(t0) {
+ this._thunk = t0;
+ this.__LazyTrace__trace_FI = $;
+ },
+ LazyTrace_terse_closure: function LazyTrace_terse_closure(t0) {
+ this.$this = t0;
+ },
+ Trace_Trace$from(trace) {
+ if (type$.Trace._is(trace))
+ return trace;
+ if (trace instanceof A.Chain)
+ return trace.toTrace$0();
+ return new A.LazyTrace(new A.Trace_Trace$from_closure(trace));
+ },
+ Trace_Trace$parse(trace) {
+ var error, t1, exception;
+ try {
+ if (trace.length === 0) {
+ t1 = A.Trace$(A._setArrayType([], type$.JSArray_Frame), null);
+ return t1;
+ }
+ if (B.JSString_methods.contains$1(trace, $.$get$_v8Trace())) {
+ t1 = A.Trace$parseV8(trace);
+ return t1;
+ }
+ if (B.JSString_methods.contains$1(trace, "\tat ")) {
+ t1 = A.Trace$parseJSCore(trace);
+ return t1;
+ }
+ if (B.JSString_methods.contains$1(trace, $.$get$_firefoxSafariTrace()) || B.JSString_methods.contains$1(trace, $.$get$_firefoxEvalTrace())) {
+ t1 = A.Trace$parseFirefox(trace);
+ return t1;
+ }
+ if (B.JSString_methods.contains$1(trace, string$.x3d_____)) {
+ t1 = A.Chain_Chain$parse(trace).toTrace$0();
+ return t1;
+ }
+ if (B.JSString_methods.contains$1(trace, $.$get$_friendlyTrace())) {
+ t1 = A.Trace$parseFriendly(trace);
+ return t1;
+ }
+ t1 = A.Trace$parseVM(trace);
+ return t1;
+ } catch (exception) {
+ t1 = A.unwrapException(exception);
+ if (t1 instanceof A.FormatException) {
+ error = t1;
+ throw A.wrapException(A.FormatException$(error.message + "\nStack trace:\n" + trace, null, null));
+ } else
+ throw exception;
+ }
+ },
+ Trace___parseVM_tearOff(trace) {
+ return A.Trace$parseVM(A._asString(trace));
+ },
+ Trace$parseVM(trace) {
+ var t1 = A.List_List$unmodifiable(A.Trace__parseVM(trace), type$.Frame);
+ return new A.Trace(t1, new A._StringStackTrace(trace));
+ },
+ Trace__parseVM(trace) {
+ var $frames,
+ t1 = B.JSString_methods.trim$0(trace),
+ t2 = $.$get$vmChainGap(),
+ t3 = type$.WhereIterable_String,
+ lines = new A.WhereIterable(A._setArrayType(A.stringReplaceAllUnchecked(t1, t2, "").split("\n"), type$.JSArray_String), type$.bool_Function_String._as(new A.Trace__parseVM_closure()), t3);
+ if (!lines.get$iterator(lines).moveNext$0())
+ return A._setArrayType([], type$.JSArray_Frame);
+ t1 = A.TakeIterable_TakeIterable(lines, lines.get$length(lines) - 1, t3._eval$1("Iterable.E"));
+ t2 = A._instanceType(t1);
+ t2 = A.MappedIterable_MappedIterable(t1, t2._eval$1("Frame(Iterable.E)")._as(A.frame_Frame___parseVM_tearOff$closure()), t2._eval$1("Iterable.E"), type$.Frame);
+ $frames = A.List_List$of(t2, true, A._instanceType(t2)._eval$1("Iterable.E"));
+ if (!J.endsWith$1$s(lines.get$last(lines), ".da"))
+ B.JSArray_methods.add$1($frames, A.Frame_Frame$parseVM(lines.get$last(lines)));
+ return $frames;
+ },
+ Trace$parseV8(trace) {
+ var t2, t3,
+ t1 = A.SubListIterable$(A._setArrayType(trace.split("\n"), type$.JSArray_String), 1, null, type$.String);
+ t1 = t1.super$Iterable$skipWhile(0, t1.$ti._eval$1("bool(ListIterable.E)")._as(new A.Trace$parseV8_closure()));
+ t2 = type$.Frame;
+ t3 = t1.$ti;
+ t2 = A.List_List$unmodifiable(A.MappedIterable_MappedIterable(t1, t3._eval$1("Frame(Iterable.E)")._as(A.frame_Frame___parseV8_tearOff$closure()), t3._eval$1("Iterable.E"), t2), t2);
+ return new A.Trace(t2, new A._StringStackTrace(trace));
+ },
+ Trace$parseJSCore(trace) {
+ var t1 = A.List_List$unmodifiable(new A.MappedIterable(new A.WhereIterable(A._setArrayType(trace.split("\n"), type$.JSArray_String), type$.bool_Function_String._as(new A.Trace$parseJSCore_closure()), type$.WhereIterable_String), type$.Frame_Function_String._as(A.frame_Frame___parseV8_tearOff$closure()), type$.MappedIterable_String_Frame), type$.Frame);
+ return new A.Trace(t1, new A._StringStackTrace(trace));
+ },
+ Trace$parseFirefox(trace) {
+ var t1 = A.List_List$unmodifiable(new A.MappedIterable(new A.WhereIterable(A._setArrayType(B.JSString_methods.trim$0(trace).split("\n"), type$.JSArray_String), type$.bool_Function_String._as(new A.Trace$parseFirefox_closure()), type$.WhereIterable_String), type$.Frame_Function_String._as(A.frame_Frame___parseFirefox_tearOff$closure()), type$.MappedIterable_String_Frame), type$.Frame);
+ return new A.Trace(t1, new A._StringStackTrace(trace));
+ },
+ Trace___parseFriendly_tearOff(trace) {
+ return A.Trace$parseFriendly(A._asString(trace));
+ },
+ Trace$parseFriendly(trace) {
+ var t1 = trace.length === 0 ? A._setArrayType([], type$.JSArray_Frame) : new A.MappedIterable(new A.WhereIterable(A._setArrayType(B.JSString_methods.trim$0(trace).split("\n"), type$.JSArray_String), type$.bool_Function_String._as(new A.Trace$parseFriendly_closure()), type$.WhereIterable_String), type$.Frame_Function_String._as(A.frame_Frame___parseFriendly_tearOff$closure()), type$.MappedIterable_String_Frame);
+ t1 = A.List_List$unmodifiable(t1, type$.Frame);
+ return new A.Trace(t1, new A._StringStackTrace(trace));
+ },
+ Trace$($frames, original) {
+ var t1 = A.List_List$unmodifiable($frames, type$.Frame);
+ return new A.Trace(t1, new A._StringStackTrace(original == null ? "" : original));
+ },
+ Trace: function Trace(t0, t1) {
+ this.frames = t0;
+ this.original = t1;
+ },
+ Trace_Trace$from_closure: function Trace_Trace$from_closure(t0) {
+ this.trace = t0;
+ },
+ Trace__parseVM_closure: function Trace__parseVM_closure() {
+ },
+ Trace$parseV8_closure: function Trace$parseV8_closure() {
+ },
+ Trace$parseJSCore_closure: function Trace$parseJSCore_closure() {
+ },
+ Trace$parseFirefox_closure: function Trace$parseFirefox_closure() {
+ },
+ Trace$parseFriendly_closure: function Trace$parseFriendly_closure() {
+ },
+ Trace_terse_closure: function Trace_terse_closure() {
+ },
+ Trace_foldFrames_closure: function Trace_foldFrames_closure(t0) {
+ this.oldPredicate = t0;
+ },
+ Trace_foldFrames_closure0: function Trace_foldFrames_closure0(t0) {
+ this._box_0 = t0;
+ },
+ Trace_toString_closure0: function Trace_toString_closure0() {
+ },
+ Trace_toString_closure: function Trace_toString_closure(t0) {
+ this.longest = t0;
+ },
+ UnparsedFrame: function UnparsedFrame(t0, t1) {
+ this.uri = t0;
+ this.member = t1;
+ },
+ GuaranteeChannel$(innerStream, innerSink, allowSinkErrors, $T) {
+ var t2, t1 = {};
+ t1.innerStream = innerStream;
+ t2 = new A.GuaranteeChannel($T._eval$1("GuaranteeChannel<0>"));
+ t2.GuaranteeChannel$3$allowSinkErrors(innerSink, true, t1, $T);
+ return t2;
+ },
+ GuaranteeChannel: function GuaranteeChannel(t0) {
+ var _ = this;
+ _.__GuaranteeChannel__streamController_F = _.__GuaranteeChannel__sink_F = $;
+ _._subscription = null;
+ _._disconnected = false;
+ _.$ti = t0;
+ },
+ GuaranteeChannel_closure: function GuaranteeChannel_closure(t0, t1, t2) {
+ this._box_0 = t0;
+ this.$this = t1;
+ this.T = t2;
+ },
+ GuaranteeChannel__closure: function GuaranteeChannel__closure(t0) {
+ this.$this = t0;
+ },
+ _GuaranteeSink: function _GuaranteeSink(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _._inner = t0;
+ _._channel = t1;
+ _._doneCompleter = t2;
+ _._closed = _._disconnected = false;
+ _._addStreamCompleter = _._addStreamSubscription = null;
+ _._allowErrors = t3;
+ _.$ti = t4;
+ },
+ _GuaranteeSink_addStream_closure: function _GuaranteeSink_addStream_closure(t0) {
+ this.$this = t0;
+ },
+ _MultiChannel$(inner, $T) {
+ var t1 = type$.int;
+ t1 = new A._MultiChannel(inner, A.StreamChannelController$(true, $T), A.LinkedHashMap_LinkedHashMap$_empty(t1, $T._eval$1("StreamChannelController<0>")), A.LinkedHashSet_LinkedHashSet$_empty(t1), A.LinkedHashSet_LinkedHashSet$_empty(t1), $T._eval$1("_MultiChannel<0>"));
+ t1._MultiChannel$1(inner, $T);
+ return t1;
+ },
+ _MultiChannel: function _MultiChannel(t0, t1, t2, t3, t4, t5) {
+ var _ = this;
+ _._multi_channel$_inner = t0;
+ _._innerStreamSubscription = null;
+ _._mainController = t1;
+ _._controllers = t2;
+ _._pendingIds = t3;
+ _._closedIds = t4;
+ _._nextId = 1;
+ _.$ti = t5;
+ },
+ _MultiChannel_closure: function _MultiChannel_closure(t0, t1) {
+ this.$this = t0;
+ this.T = t1;
+ },
+ _MultiChannel_closure0: function _MultiChannel_closure0(t0) {
+ this.$this = t0;
+ },
+ _MultiChannel_closure1: function _MultiChannel_closure1(t0, t1) {
+ this.$this = t0;
+ this.T = t1;
+ },
+ _MultiChannel__closure: function _MultiChannel__closure(t0, t1, t2) {
+ this.$this = t0;
+ this.id = t1;
+ this.T = t2;
+ },
+ _MultiChannel_virtualChannel_closure: function _MultiChannel_virtualChannel_closure(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ _MultiChannel_virtualChannel_closure0: function _MultiChannel_virtualChannel_closure0(t0, t1) {
+ this._box_0 = t0;
+ this.$this = t1;
+ },
+ VirtualChannel: function VirtualChannel(t0, t1, t2, t3) {
+ var _ = this;
+ _._parent = t0;
+ _.stream = t1;
+ _.sink = t2;
+ _.$ti = t3;
+ },
+ StreamChannelController$(sync, $T) {
+ var _null = null,
+ t1 = new A.StreamChannelController($T._eval$1("StreamChannelController<0>")),
+ localToForeignController = A.StreamController_StreamController(_null, _null, true, $T),
+ foreignToLocalController = A.StreamController_StreamController(_null, _null, true, $T),
+ t2 = A._instanceType(foreignToLocalController),
+ t3 = A._instanceType(localToForeignController),
+ t4 = $T._eval$1("StreamChannel<0>");
+ t1.set$__StreamChannelController__local_F(t4._as(A.GuaranteeChannel$(new A._ControllerStream(foreignToLocalController, t2._eval$1("_ControllerStream<1>")), new A._StreamSinkWrapper(localToForeignController, t3._eval$1("_StreamSinkWrapper<1>")), true, $T)));
+ t2 = t4._as(A.GuaranteeChannel$(new A._ControllerStream(localToForeignController, t3._eval$1("_ControllerStream<1>")), new A._StreamSinkWrapper(foreignToLocalController, t2._eval$1("_StreamSinkWrapper<1>")), true, $T));
+ t1.__StreamChannelController__foreign_F !== $ && A.throwLateFieldAI("_foreign");
+ t1.set$__StreamChannelController__foreign_F(t2);
+ return t1;
+ },
+ StreamChannelController: function StreamChannelController(t0) {
+ this.__StreamChannelController__foreign_F = this.__StreamChannelController__local_F = $;
+ this.$ti = t0;
+ },
+ StreamChannelMixin: function StreamChannelMixin() {
+ },
+ EventTargetExtension_addEventListener(_this, type, listener) {
+ var t1 = A._setArrayType([type, listener], type$.JSArray_Object);
+ A.callMethod(_this, "addEventListener", t1, type$.dynamic);
+ },
+ EventTargetExtension_removeEventListener(_this, type, listener) {
+ var t1 = A._setArrayType([type, listener], type$.JSArray_Object);
+ A.callMethod(_this, "removeEventListener", t1, type$.dynamic);
+ },
+ MessagePortExtension_get_postMessage(_this) {
+ return new A.MessagePortExtension_get_postMessage_closure(_this);
+ },
+ _callConstructor(constructorName, args) {
+ var $constructor = self.window[constructorName];
+ if ($constructor == null)
+ return null;
+ return A.callConstructor($constructor, args, type$.nullable_Object);
+ },
+ Subscription$(target, type, listener) {
+ A.EventTargetExtension_addEventListener(target, type, listener);
+ return new A.Subscription(type, target, listener);
+ },
+ MessagePortExtension_get_postMessage_closure: function MessagePortExtension_get_postMessage_closure(t0) {
+ this._this = t0;
+ },
+ Subscription: function Subscription(t0, t1, t2) {
+ this.type = t0;
+ this.target = t1;
+ this.listener = t2;
+ },
+ main() {
+ var t1 = type$.JavaScriptObject;
+ t1._as(self.window.console).log("Dart test runner browser host running");
+ if (J.$eq$($.$get$_currentUrl().get$queryParameters().$index(0, "debug"), "true"))
+ t1._as(type$.nullable_JavaScriptObject._as(self.document.body).classList).add("debug");
+ A.runZonedGuarded(new A.main_closure(), new A.main_closure0(), type$.Null);
+ },
+ _connectToServer() {
+ var t2, controller, t3,
+ t1 = $.$get$_currentUrl().get$queryParameters().$index(0, "managerUrl");
+ t1.toString;
+ t1 = A._callConstructor("WebSocket", A._setArrayType([t1], type$.JSArray_Object));
+ t1.toString;
+ type$.JavaScriptObject._as(t1);
+ t2 = type$.dynamic;
+ controller = A.StreamChannelController$(true, t2);
+ A.EventTargetExtension_addEventListener(t1, "message", A.allowInterop(new A._connectToServer_closure(controller), type$.void_Function_JavaScriptObject));
+ t3 = controller.__StreamChannelController__local_F;
+ t3 === $ && A.throwLateFieldNI("_local");
+ t3 = t3.__GuaranteeChannel__streamController_F;
+ t3 === $ && A.throwLateFieldNI("_streamController");
+ new A._ControllerStream(t3, A._instanceType(t3)._eval$1("_ControllerStream<1>")).listen$1(new A._connectToServer_closure0(t1));
+ t1 = controller.__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI("_foreign");
+ return A._MultiChannel$(t1, t2);
+ },
+ _connectToIframe(url, id) {
+ var t2, t3, t4, iframe, controller, windowSubscription,
+ suiteUrl = A.Uri_parse(url).removeFragment$0(),
+ t1 = type$.JavaScriptObject;
+ t1._as(self.window.console).log("Starting suite " + suiteUrl.toString$0(0));
+ t2 = self.document;
+ t3 = A._setArrayType(["iframe"], type$.JSArray_Object);
+ t4 = type$.dynamic;
+ iframe = t1._as(A.callMethod(t2, "createElement", t3, t4));
+ $._iframes.$indexSet(0, id, iframe);
+ controller = A.StreamChannelController$(true, t4);
+ windowSubscription = A._Cell$named("windowSubscription");
+ windowSubscription._value = A.Subscription$(self.window, "message", A.allowInterop(new A._connectToIframe_closure(iframe, windowSubscription, suiteUrl, id, controller), type$.void_Function_JavaScriptObject));
+ iframe.src = url;
+ t1._as(type$.nullable_JavaScriptObject._as(self.document.body).appendChild(iframe));
+ t1 = controller.__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI("_foreign");
+ return t1;
+ },
+ main_closure: function main_closure() {
+ },
+ main__closure: function main__closure(t0) {
+ this.serverChannel = t0;
+ },
+ main___closure: function main___closure(t0) {
+ this._0_0 = t0;
+ },
+ main___closure0: function main___closure0(t0) {
+ this._0_0 = t0;
+ },
+ main___closure1: function main___closure1(t0) {
+ this._0_0 = t0;
+ },
+ main___closure2: function main___closure2(t0) {
+ this._0_0 = t0;
+ },
+ main___closure3: function main___closure3(t0) {
+ this._0_0 = t0;
+ },
+ main___closure4: function main___closure4(t0) {
+ this._0_0 = t0;
+ },
+ main__closure0: function main__closure0(t0) {
+ this.serverChannel = t0;
+ },
+ main__closure1: function main__closure1(t0) {
+ this.serverChannel = t0;
+ },
+ main__closure2: function main__closure2(t0) {
+ this.serverChannel = t0;
+ },
+ main__closure3: function main__closure3(t0) {
+ this.serverChannel = t0;
+ },
+ main_closure0: function main_closure0() {
+ },
+ _connectToServer_closure: function _connectToServer_closure(t0) {
+ this.controller = t0;
+ },
+ _connectToServer_closure0: function _connectToServer_closure0(t0) {
+ this.webSocket = t0;
+ },
+ _connectToIframe_closure: function _connectToIframe_closure(t0, t1, t2, t3, t4) {
+ var _ = this;
+ _.iframe = t0;
+ _.windowSubscription = t1;
+ _.suiteUrl = t2;
+ _.id = t3;
+ _.controller = t4;
+ },
+ _connectToIframe__closure1: function _connectToIframe__closure1(t0) {
+ this.controller = t0;
+ },
+ _connectToIframe__closure: function _connectToIframe__closure(t0) {
+ this._0_0 = t0;
+ },
+ _connectToIframe__closure0: function _connectToIframe__closure0(t0) {
+ this._0_0 = t0;
+ },
+ max(a, b, $T) {
+ A.checkTypeBound($T, type$.num, "T", "max");
+ return Math.max($T._as(a), $T._as(b));
+ },
+ printString(string) {
+ if (typeof dartPrint == "function") {
+ dartPrint(string);
+ return;
+ }
+ if (typeof console == "object" && typeof console.log != "undefined") {
+ console.log(string);
+ return;
+ }
+ if (typeof print == "function") {
+ print(string);
+ return;
+ }
+ throw "Unable to print message: " + String(string);
+ },
+ _convertNativeToDart_Value(value) {
+ var proto, t1, values, i;
+ if (value == null)
+ return value;
+ if (typeof value == "string" || typeof value == "number" || A._isBool(value))
+ return value;
+ proto = Object.getPrototypeOf(value);
+ t1 = proto === Object.prototype;
+ t1.toString;
+ if (!t1) {
+ t1 = proto === null;
+ t1.toString;
+ } else
+ t1 = true;
+ if (t1)
+ return A.convertNativeToDart_Dictionary(value);
+ t1 = Array.isArray(value);
+ t1.toString;
+ if (t1) {
+ values = [];
+ i = 0;
+ while (true) {
+ t1 = value.length;
+ t1.toString;
+ if (!(i < t1))
+ break;
+ values.push(A._convertNativeToDart_Value(value[i]));
+ ++i;
+ }
+ return values;
+ }
+ return value;
+ },
+ convertNativeToDart_Dictionary(object) {
+ var dict, keys, t1, _i, key, t2;
+ if (object == null)
+ return null;
+ dict = A.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.dynamic);
+ keys = Object.getOwnPropertyNames(object);
+ for (t1 = keys.length, _i = 0; _i < keys.length; keys.length === t1 || (0, A.throwConcurrentModificationError)(keys), ++_i) {
+ key = keys[_i];
+ t2 = key;
+ t2.toString;
+ dict.$indexSet(0, t2, A._convertNativeToDart_Value(object[key]));
+ }
+ return dict;
+ },
+ current() {
+ var exception, t1, path, lastIndex, uri = null;
+ try {
+ uri = A.Uri_base();
+ } catch (exception) {
+ if (type$.Exception._is(A.unwrapException(exception))) {
+ t1 = $._current;
+ if (t1 != null)
+ return t1;
+ throw exception;
+ } else
+ throw exception;
+ }
+ if (J.$eq$(uri, $._currentUriBase)) {
+ t1 = $._current;
+ t1.toString;
+ return t1;
+ }
+ $._currentUriBase = uri;
+ if ($.$get$Style_platform() === $.$get$Style_url())
+ t1 = $._current = uri.resolve$1(".").toString$0(0);
+ else {
+ path = uri.toFilePath$0();
+ lastIndex = path.length - 1;
+ t1 = $._current = lastIndex === 0 ? path : B.JSString_methods.substring$2(path, 0, lastIndex);
+ }
+ return t1;
+ },
+ isAlphabetic(char) {
+ var t1;
+ if (!(char >= 65 && char <= 90))
+ t1 = char >= 97 && char <= 122;
+ else
+ t1 = true;
+ return t1;
+ },
+ isDriveLetter(path, index) {
+ var t3,
+ t1 = path.length,
+ t2 = index + 2;
+ if (t1 < t2)
+ return false;
+ if (!(index >= 0 && index < t1))
+ return A.ioore(path, index);
+ if (!A.isAlphabetic(path.charCodeAt(index)))
+ return false;
+ t3 = index + 1;
+ if (!(t3 < t1))
+ return A.ioore(path, t3);
+ if (path.charCodeAt(t3) !== 58)
+ return false;
+ if (t1 === t2)
+ return true;
+ if (!(t2 >= 0 && t2 < t1))
+ return A.ioore(path, t2);
+ return path.charCodeAt(t2) === 47;
+ }
+ },
+ B = {};
+ var holders = [A, J, B];
+ var $ = {};
+ A.JS_CONST.prototype = {};
+ J.Interceptor.prototype = {
+ $eq(receiver, other) {
+ return receiver === other;
+ },
+ get$hashCode(receiver) {
+ return A.Primitives_objectHashCode(receiver);
+ },
+ toString$0(receiver) {
+ return "Instance of '" + A.Primitives_objectTypeName(receiver) + "'";
+ },
+ noSuchMethod$1(receiver, invocation) {
+ throw A.wrapException(A.NoSuchMethodError_NoSuchMethodError$withInvocation(receiver, type$.Invocation._as(invocation)));
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(A._instanceTypeFromConstructor(this));
+ }
+ };
+ J.JSBool.prototype = {
+ toString$0(receiver) {
+ return String(receiver);
+ },
+ get$hashCode(receiver) {
+ return receiver ? 519018 : 218159;
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.bool);
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isbool: 1
+ };
+ J.JSNull.prototype = {
+ $eq(receiver, other) {
+ return null == other;
+ },
+ toString$0(receiver) {
+ return "null";
+ },
+ get$hashCode(receiver) {
+ return 0;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isNull: 1
+ };
+ J.JavaScriptObject.prototype = {};
+ J.LegacyJavaScriptObject.prototype = {
+ get$hashCode(receiver) {
+ return 0;
+ },
+ toString$0(receiver) {
+ return String(receiver);
+ }
+ };
+ J.PlainJavaScriptObject.prototype = {};
+ J.UnknownJavaScriptObject.prototype = {};
+ J.JavaScriptFunction.prototype = {
+ toString$0(receiver) {
+ var dartClosure = receiver[$.$get$DART_CLOSURE_PROPERTY_NAME()];
+ if (dartClosure == null)
+ return this.super$LegacyJavaScriptObject$toString(receiver);
+ return "JavaScript function for " + J.toString$0$(dartClosure);
+ },
+ $isFunction: 1
+ };
+ J.JavaScriptBigInt.prototype = {
+ get$hashCode(receiver) {
+ return 0;
+ },
+ toString$0(receiver) {
+ return String(receiver);
+ }
+ };
+ J.JavaScriptSymbol.prototype = {
+ get$hashCode(receiver) {
+ return 0;
+ },
+ toString$0(receiver) {
+ return String(receiver);
+ }
+ };
+ J.JSArray.prototype = {
+ cast$1$0(receiver, $R) {
+ return new A.CastList(receiver, A._arrayInstanceType(receiver)._eval$1("@<1>")._bind$1($R)._eval$1("CastList<1,2>"));
+ },
+ add$1(receiver, value) {
+ A._arrayInstanceType(receiver)._precomputed1._as(value);
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("add"));
+ receiver.push(value);
+ },
+ removeAt$1(receiver, index) {
+ var t1;
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("removeAt"));
+ t1 = receiver.length;
+ if (index >= t1)
+ throw A.wrapException(A.RangeError$value(index, null));
+ return receiver.splice(index, 1)[0];
+ },
+ insert$2(receiver, index, value) {
+ var t1;
+ A._arrayInstanceType(receiver)._precomputed1._as(value);
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("insert"));
+ t1 = receiver.length;
+ if (index > t1)
+ throw A.wrapException(A.RangeError$value(index, null));
+ receiver.splice(index, 0, value);
+ },
+ insertAll$2(receiver, index, iterable) {
+ var insertionLength, end;
+ A._arrayInstanceType(receiver)._eval$1("Iterable<1>")._as(iterable);
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("insertAll"));
+ A.RangeError_checkValueInInterval(index, 0, receiver.length, "index");
+ if (!type$.EfficientLengthIterable_dynamic._is(iterable))
+ iterable = J.toList$0$ax(iterable);
+ insertionLength = J.get$length$asx(iterable);
+ receiver.length = receiver.length + insertionLength;
+ end = index + insertionLength;
+ this.setRange$4(receiver, end, receiver.length, receiver, index);
+ this.setRange$3(receiver, index, end, iterable);
+ },
+ removeLast$0(receiver) {
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("removeLast"));
+ if (receiver.length === 0)
+ throw A.wrapException(A.diagnoseIndexError(receiver, -1));
+ return receiver.pop();
+ },
+ addAll$1(receiver, collection) {
+ var t1;
+ A._arrayInstanceType(receiver)._eval$1("Iterable<1>")._as(collection);
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("addAll"));
+ if (Array.isArray(collection)) {
+ this._addAllFromArray$1(receiver, collection);
+ return;
+ }
+ for (t1 = J.get$iterator$ax(collection); t1.moveNext$0();)
+ receiver.push(t1.get$current(t1));
+ },
+ _addAllFromArray$1(receiver, array) {
+ var len, i;
+ type$.JSArray_dynamic._as(array);
+ len = array.length;
+ if (len === 0)
+ return;
+ if (receiver === array)
+ throw A.wrapException(A.ConcurrentModificationError$(receiver));
+ for (i = 0; i < len; ++i)
+ receiver.push(array[i]);
+ },
+ clear$0(receiver) {
+ if (!!receiver.fixed$length)
+ A.throwExpression(A.UnsupportedError$("clear"));
+ receiver.length = 0;
+ },
+ map$1$1(receiver, f, $T) {
+ var t1 = A._arrayInstanceType(receiver);
+ return new A.MappedListIterable(receiver, t1._bind$1($T)._eval$1("1(2)")._as(f), t1._eval$1("@<1>")._bind$1($T)._eval$1("MappedListIterable<1,2>"));
+ },
+ join$1(receiver, separator) {
+ var i,
+ list = A.List_List$filled(receiver.length, "", false, type$.String);
+ for (i = 0; i < receiver.length; ++i)
+ this.$indexSet(list, i, A.S(receiver[i]));
+ return list.join(separator);
+ },
+ join$0($receiver) {
+ return this.join$1($receiver, "");
+ },
+ skip$1(receiver, n) {
+ return A.SubListIterable$(receiver, n, null, A._arrayInstanceType(receiver)._precomputed1);
+ },
+ fold$1$2(receiver, initialValue, combine, $T) {
+ var $length, value, i;
+ $T._as(initialValue);
+ A._arrayInstanceType(receiver)._bind$1($T)._eval$1("1(1,2)")._as(combine);
+ $length = receiver.length;
+ for (value = initialValue, i = 0; i < $length; ++i) {
+ value = combine.call$2(value, receiver[i]);
+ if (receiver.length !== $length)
+ throw A.wrapException(A.ConcurrentModificationError$(receiver));
+ }
+ return value;
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ get$first(receiver) {
+ if (receiver.length > 0)
+ return receiver[0];
+ throw A.wrapException(A.IterableElementError_noElement());
+ },
+ get$last(receiver) {
+ var t1 = receiver.length;
+ if (t1 > 0)
+ return receiver[t1 - 1];
+ throw A.wrapException(A.IterableElementError_noElement());
+ },
+ setRange$4(receiver, start, end, iterable, skipCount) {
+ var $length, otherList, otherStart, t1, i;
+ A._arrayInstanceType(receiver)._eval$1("Iterable<1>")._as(iterable);
+ if (!!receiver.immutable$list)
+ A.throwExpression(A.UnsupportedError$("setRange"));
+ A.RangeError_checkValidRange(start, end, receiver.length);
+ $length = end - start;
+ if ($length === 0)
+ return;
+ A.RangeError_checkNotNegative(skipCount, "skipCount");
+ if (type$.List_dynamic._is(iterable)) {
+ otherList = iterable;
+ otherStart = skipCount;
+ } else {
+ otherList = J.skip$1$ax(iterable, skipCount).toList$1$growable(0, false);
+ otherStart = 0;
+ }
+ t1 = J.getInterceptor$asx(otherList);
+ if (otherStart + $length > t1.get$length(otherList))
+ throw A.wrapException(A.IterableElementError_tooFew());
+ if (otherStart < start)
+ for (i = $length - 1; i >= 0; --i)
+ receiver[start + i] = t1.$index(otherList, otherStart + i);
+ else
+ for (i = 0; i < $length; ++i)
+ receiver[start + i] = t1.$index(otherList, otherStart + i);
+ },
+ setRange$3($receiver, start, end, iterable) {
+ return this.setRange$4($receiver, start, end, iterable, 0);
+ },
+ contains$1(receiver, other) {
+ var i;
+ for (i = 0; i < receiver.length; ++i)
+ if (J.$eq$(receiver[i], other))
+ return true;
+ return false;
+ },
+ get$isEmpty(receiver) {
+ return receiver.length === 0;
+ },
+ get$isNotEmpty(receiver) {
+ return receiver.length !== 0;
+ },
+ toString$0(receiver) {
+ return A.Iterable_iterableToFullString(receiver, "[", "]");
+ },
+ toList$1$growable(receiver, growable) {
+ var t1 = A._setArrayType(receiver.slice(0), A._arrayInstanceType(receiver));
+ return t1;
+ },
+ toList$0($receiver) {
+ return this.toList$1$growable($receiver, true);
+ },
+ get$iterator(receiver) {
+ return new J.ArrayIterator(receiver, receiver.length, A._arrayInstanceType(receiver)._eval$1("ArrayIterator<1>"));
+ },
+ get$hashCode(receiver) {
+ return A.Primitives_objectHashCode(receiver);
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $index(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ throw A.wrapException(A.diagnoseIndexError(receiver, index));
+ return receiver[index];
+ },
+ $indexSet(receiver, index, value) {
+ A._arrayInstanceType(receiver)._precomputed1._as(value);
+ if (!!receiver.immutable$list)
+ A.throwExpression(A.UnsupportedError$("indexed set"));
+ if (!(index >= 0 && index < receiver.length))
+ throw A.wrapException(A.diagnoseIndexError(receiver, index));
+ receiver[index] = value;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ J.JSUnmodifiableArray.prototype = {};
+ J.ArrayIterator.prototype = {
+ get$current(_) {
+ var t1 = this._current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var t2, _this = this,
+ t1 = _this._iterable,
+ $length = t1.length;
+ if (_this.__interceptors$_length !== $length) {
+ t1 = A.throwConcurrentModificationError(t1);
+ throw A.wrapException(t1);
+ }
+ t2 = _this._index;
+ if (t2 >= $length) {
+ _this.set$_current(null);
+ return false;
+ }
+ _this.set$_current(t1[t2]);
+ ++_this._index;
+ return true;
+ },
+ set$_current(_current) {
+ this._current = this.$ti._eval$1("1?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ J.JSNumber.prototype = {
+ toInt$0(receiver) {
+ var t1;
+ if (receiver >= -2147483648 && receiver <= 2147483647)
+ return receiver | 0;
+ if (isFinite(receiver)) {
+ t1 = receiver < 0 ? Math.ceil(receiver) : Math.floor(receiver);
+ return t1 + 0;
+ }
+ throw A.wrapException(A.UnsupportedError$("" + receiver + ".toInt()"));
+ },
+ toString$0(receiver) {
+ if (receiver === 0 && 1 / receiver < 0)
+ return "-0.0";
+ else
+ return "" + receiver;
+ },
+ get$hashCode(receiver) {
+ var absolute, floorLog2, factor, scaled,
+ intValue = receiver | 0;
+ if (receiver === intValue)
+ return intValue & 536870911;
+ absolute = Math.abs(receiver);
+ floorLog2 = Math.log(absolute) / 0.6931471805599453 | 0;
+ factor = Math.pow(2, floorLog2);
+ scaled = absolute < 1 ? absolute / factor : factor / absolute;
+ return ((scaled * 9007199254740992 | 0) + (scaled * 3542243181176521 | 0)) * 599197 + floorLog2 * 1259 & 536870911;
+ },
+ $mod(receiver, other) {
+ var result = receiver % other;
+ if (result === 0)
+ return 0;
+ if (result > 0)
+ return result;
+ return result + other;
+ },
+ $tdiv(receiver, other) {
+ if ((receiver | 0) === receiver)
+ if (other >= 1 || false)
+ return receiver / other | 0;
+ return this._tdivSlow$1(receiver, other);
+ },
+ _tdivFast$1(receiver, other) {
+ return (receiver | 0) === receiver ? receiver / other | 0 : this._tdivSlow$1(receiver, other);
+ },
+ _tdivSlow$1(receiver, other) {
+ var quotient = receiver / other;
+ if (quotient >= -2147483648 && quotient <= 2147483647)
+ return quotient | 0;
+ if (quotient > 0) {
+ if (quotient !== 1 / 0)
+ return Math.floor(quotient);
+ } else if (quotient > -1 / 0)
+ return Math.ceil(quotient);
+ throw A.wrapException(A.UnsupportedError$("Result of truncating division is " + A.S(quotient) + ": " + A.S(receiver) + " ~/ " + other));
+ },
+ _shrOtherPositive$1(receiver, other) {
+ var t1;
+ if (receiver > 0)
+ t1 = this._shrBothPositive$1(receiver, other);
+ else {
+ t1 = other > 31 ? 31 : other;
+ t1 = receiver >> t1 >>> 0;
+ }
+ return t1;
+ },
+ _shrReceiverPositive$1(receiver, other) {
+ if (0 > other)
+ throw A.wrapException(A.argumentErrorValue(other));
+ return this._shrBothPositive$1(receiver, other);
+ },
+ _shrBothPositive$1(receiver, other) {
+ return other > 31 ? 0 : receiver >>> other;
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.num);
+ },
+ $isdouble: 1,
+ $isnum: 1
+ };
+ J.JSInt.prototype = {
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.int);
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isint: 1
+ };
+ J.JSNumNotInt.prototype = {
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.double);
+ },
+ $isTrustedGetRuntimeType: 1
+ };
+ J.JSString.prototype = {
+ codeUnitAt$1(receiver, index) {
+ if (index < 0)
+ throw A.wrapException(A.diagnoseIndexError(receiver, index));
+ if (index >= receiver.length)
+ A.throwExpression(A.diagnoseIndexError(receiver, index));
+ return receiver.charCodeAt(index);
+ },
+ allMatches$2(receiver, string, start) {
+ var t1 = string.length;
+ if (start > t1)
+ throw A.wrapException(A.RangeError$range(start, 0, t1, null, null));
+ return new A._StringAllMatchesIterable(string, receiver, start);
+ },
+ allMatches$1($receiver, string) {
+ return this.allMatches$2($receiver, string, 0);
+ },
+ matchAsPrefix$2(receiver, string, start) {
+ var t1, t2, i, t3, _null = null;
+ if (start < 0 || start > string.length)
+ throw A.wrapException(A.RangeError$range(start, 0, string.length, _null, _null));
+ t1 = receiver.length;
+ t2 = string.length;
+ if (start + t1 > t2)
+ return _null;
+ for (i = 0; i < t1; ++i) {
+ t3 = start + i;
+ if (!(t3 >= 0 && t3 < t2))
+ return A.ioore(string, t3);
+ if (string.charCodeAt(t3) !== receiver.charCodeAt(i))
+ return _null;
+ }
+ return new A.StringMatch(start, receiver);
+ },
+ $add(receiver, other) {
+ return receiver + other;
+ },
+ endsWith$1(receiver, other) {
+ var otherLength = other.length,
+ t1 = receiver.length;
+ if (otherLength > t1)
+ return false;
+ return other === this.substring$1(receiver, t1 - otherLength);
+ },
+ replaceFirst$2(receiver, from, to) {
+ A.RangeError_checkValueInInterval(0, 0, receiver.length, "startIndex");
+ return A.stringReplaceFirstUnchecked(receiver, from, to, 0);
+ },
+ split$1(receiver, pattern) {
+ if (typeof pattern == "string")
+ return A._setArrayType(receiver.split(pattern), type$.JSArray_String);
+ else if (pattern instanceof A.JSSyntaxRegExp && pattern.get$_nativeAnchoredVersion().exec("").length - 2 === 0)
+ return A._setArrayType(receiver.split(pattern._nativeRegExp), type$.JSArray_String);
+ else
+ return this._defaultSplit$1(receiver, pattern);
+ },
+ replaceRange$3(receiver, start, end, replacement) {
+ var e = A.RangeError_checkValidRange(start, end, receiver.length);
+ return A.stringReplaceRangeUnchecked(receiver, start, e, replacement);
+ },
+ _defaultSplit$1(receiver, pattern) {
+ var t1, start, $length, match, matchStart, matchEnd,
+ result = A._setArrayType([], type$.JSArray_String);
+ for (t1 = J.allMatches$1$s(pattern, receiver), t1 = t1.get$iterator(t1), start = 0, $length = 1; t1.moveNext$0();) {
+ match = t1.get$current(t1);
+ matchStart = match.get$start(match);
+ matchEnd = match.get$end(match);
+ $length = matchEnd - matchStart;
+ if ($length === 0 && start === matchStart)
+ continue;
+ B.JSArray_methods.add$1(result, this.substring$2(receiver, start, matchStart));
+ start = matchEnd;
+ }
+ if (start < receiver.length || $length > 0)
+ B.JSArray_methods.add$1(result, this.substring$1(receiver, start));
+ return result;
+ },
+ startsWith$2(receiver, pattern, index) {
+ var endIndex;
+ if (index < 0 || index > receiver.length)
+ throw A.wrapException(A.RangeError$range(index, 0, receiver.length, null, null));
+ if (typeof pattern == "string") {
+ endIndex = index + pattern.length;
+ if (endIndex > receiver.length)
+ return false;
+ return pattern === receiver.substring(index, endIndex);
+ }
+ return J.matchAsPrefix$2$s(pattern, receiver, index) != null;
+ },
+ startsWith$1($receiver, pattern) {
+ return this.startsWith$2($receiver, pattern, 0);
+ },
+ substring$2(receiver, start, end) {
+ return receiver.substring(start, A.RangeError_checkValidRange(start, end, receiver.length));
+ },
+ substring$1($receiver, start) {
+ return this.substring$2($receiver, start, null);
+ },
+ trim$0(receiver) {
+ var startIndex, t1, endIndex0,
+ result = receiver.trim(),
+ endIndex = result.length;
+ if (endIndex === 0)
+ return result;
+ if (0 >= endIndex)
+ return A.ioore(result, 0);
+ if (result.charCodeAt(0) === 133) {
+ startIndex = J.JSString__skipLeadingWhitespace(result, 1);
+ if (startIndex === endIndex)
+ return "";
+ } else
+ startIndex = 0;
+ t1 = endIndex - 1;
+ if (!(t1 >= 0))
+ return A.ioore(result, t1);
+ endIndex0 = result.charCodeAt(t1) === 133 ? J.JSString__skipTrailingWhitespace(result, t1) : endIndex;
+ if (startIndex === 0 && endIndex0 === endIndex)
+ return result;
+ return result.substring(startIndex, endIndex0);
+ },
+ $mul(receiver, times) {
+ var s, result;
+ if (0 >= times)
+ return "";
+ if (times === 1 || receiver.length === 0)
+ return receiver;
+ if (times !== times >>> 0)
+ throw A.wrapException(B.C_OutOfMemoryError);
+ for (s = receiver, result = ""; true;) {
+ if ((times & 1) === 1)
+ result = s + result;
+ times = times >>> 1;
+ if (times === 0)
+ break;
+ s += s;
+ }
+ return result;
+ },
+ padLeft$2(receiver, width, padding) {
+ var delta = width - receiver.length;
+ if (delta <= 0)
+ return receiver;
+ return this.$mul(padding, delta) + receiver;
+ },
+ padRight$1(receiver, width) {
+ var delta = width - receiver.length;
+ if (delta <= 0)
+ return receiver;
+ return receiver + this.$mul(" ", delta);
+ },
+ indexOf$2(receiver, pattern, start) {
+ var t1;
+ if (start < 0 || start > receiver.length)
+ throw A.wrapException(A.RangeError$range(start, 0, receiver.length, null, null));
+ t1 = receiver.indexOf(pattern, start);
+ return t1;
+ },
+ indexOf$1($receiver, pattern) {
+ return this.indexOf$2($receiver, pattern, 0);
+ },
+ lastIndexOf$2(receiver, pattern, start) {
+ var t1, t2;
+ if (start == null)
+ start = receiver.length;
+ else if (start < 0 || start > receiver.length)
+ throw A.wrapException(A.RangeError$range(start, 0, receiver.length, null, null));
+ t1 = pattern.length;
+ t2 = receiver.length;
+ if (start + t1 > t2)
+ start = t2 - t1;
+ return receiver.lastIndexOf(pattern, start);
+ },
+ lastIndexOf$1($receiver, pattern) {
+ return this.lastIndexOf$2($receiver, pattern, null);
+ },
+ contains$1(receiver, other) {
+ return A.stringContainsUnchecked(receiver, other, 0);
+ },
+ toString$0(receiver) {
+ return receiver;
+ },
+ get$hashCode(receiver) {
+ var t1, hash, i;
+ for (t1 = receiver.length, hash = 0, i = 0; i < t1; ++i) {
+ hash = hash + receiver.charCodeAt(i) & 536870911;
+ hash = hash + ((hash & 524287) << 10) & 536870911;
+ hash ^= hash >> 6;
+ }
+ hash = hash + ((hash & 67108863) << 3) & 536870911;
+ hash ^= hash >> 11;
+ return hash + ((hash & 16383) << 15) & 536870911;
+ },
+ get$runtimeType(receiver) {
+ return A.createRuntimeType(type$.String);
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isPattern: 1,
+ $isString: 1
+ };
+ A.CastStream.prototype = {
+ listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, onError) {
+ var t2,
+ t1 = this.$ti;
+ t1._eval$1("~(2)?")._as(onData);
+ t2 = this._source.listen$3$cancelOnError$onDone(null, cancelOnError, type$.nullable_void_Function._as(onDone));
+ t1 = new A.CastStreamSubscription(t2, $.Zone__current, t1._eval$1("@<1>")._bind$1(t1._rest[1])._eval$1("CastStreamSubscription<1,2>"));
+ t2.onData$1(t1.get$__internal$_onData());
+ t1.onData$1(onData);
+ t1.onError$1(0, onError);
+ return t1;
+ },
+ listen$1(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ },
+ listen$3$onDone$onError(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$3$cancelOnError$onDone(onData, cancelOnError, onDone) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, null);
+ }
+ };
+ A.CastStreamSubscription.prototype = {
+ cancel$0(_) {
+ return this._source.cancel$0(0);
+ },
+ onData$1(handleData) {
+ var t1 = this.$ti;
+ t1._eval$1("~(2)?")._as(handleData);
+ this.set$_handleData(handleData == null ? null : this.__internal$_zone.registerUnaryCallback$2$1(handleData, type$.dynamic, t1._rest[1]));
+ },
+ onError$1(_, handleError) {
+ var _this = this;
+ _this._source.onError$1(0, handleError);
+ if (handleError == null)
+ _this._handleError = null;
+ else if (type$.void_Function_Object_StackTrace._is(handleError))
+ _this._handleError = _this.__internal$_zone.registerBinaryCallback$3$1(handleError, type$.dynamic, type$.Object, type$.StackTrace);
+ else if (type$.void_Function_Object._is(handleError))
+ _this._handleError = _this.__internal$_zone.registerUnaryCallback$2$1(handleError, type$.dynamic, type$.Object);
+ else
+ throw A.wrapException(A.ArgumentError$(string$.handle, null));
+ },
+ __internal$_onData$1(data) {
+ var targetData, error, stack, handleError, t2, exception, _this = this,
+ t1 = _this.$ti;
+ t1._precomputed1._as(data);
+ t2 = _this._handleData;
+ if (t2 == null)
+ return;
+ targetData = null;
+ try {
+ targetData = t1._rest[1]._as(data);
+ } catch (exception) {
+ error = A.unwrapException(exception);
+ stack = A.getTraceFromException(exception);
+ handleError = _this._handleError;
+ if (handleError == null)
+ _this.__internal$_zone.handleUncaughtError$2(error, stack);
+ else {
+ t1 = type$.Object;
+ t2 = _this.__internal$_zone;
+ if (type$.void_Function_Object_StackTrace._is(handleError))
+ t2.runBinaryGuarded$2$3(handleError, error, stack, t1, type$.StackTrace);
+ else
+ t2.runUnaryGuarded$1$2(type$.void_Function_Object._as(handleError), error, t1);
+ }
+ return;
+ }
+ _this.__internal$_zone.runUnaryGuarded$1$2(t2, targetData, t1._rest[1]);
+ },
+ set$_handleData(_handleData) {
+ this._handleData = this.$ti._eval$1("~(2)?")._as(_handleData);
+ },
+ $isStreamSubscription: 1
+ };
+ A._CastIterableBase.prototype = {
+ get$iterator(_) {
+ var t1 = A._instanceType(this);
+ return new A.CastIterator(J.get$iterator$ax(this.get$_source()), t1._eval$1("@<1>")._bind$1(t1._rest[1])._eval$1("CastIterator<1,2>"));
+ },
+ get$length(_) {
+ return J.get$length$asx(this.get$_source());
+ },
+ get$isEmpty(_) {
+ return J.get$isEmpty$asx(this.get$_source());
+ },
+ get$isNotEmpty(_) {
+ return J.get$isNotEmpty$asx(this.get$_source());
+ },
+ skip$1(_, count) {
+ var t1 = A._instanceType(this);
+ return A.CastIterable_CastIterable(J.skip$1$ax(this.get$_source(), count), t1._precomputed1, t1._rest[1]);
+ },
+ elementAt$1(_, index) {
+ return A._instanceType(this)._rest[1]._as(J.elementAt$1$ax(this.get$_source(), index));
+ },
+ toString$0(_) {
+ return J.toString$0$(this.get$_source());
+ }
+ };
+ A.CastIterator.prototype = {
+ moveNext$0() {
+ return this._source.moveNext$0();
+ },
+ get$current(_) {
+ var t1 = this._source;
+ return this.$ti._rest[1]._as(t1.get$current(t1));
+ },
+ $isIterator: 1
+ };
+ A.CastIterable.prototype = {
+ get$_source() {
+ return this._source;
+ }
+ };
+ A._EfficientLengthCastIterable.prototype = {$isEfficientLengthIterable: 1};
+ A._CastListBase.prototype = {
+ $index(_, index) {
+ return this.$ti._rest[1]._as(J.$index$asx(this._source, index));
+ },
+ $indexSet(_, index, value) {
+ var t1 = this.$ti;
+ J.$indexSet$ax(this._source, index, t1._precomputed1._as(t1._rest[1]._as(value)));
+ },
+ $isEfficientLengthIterable: 1,
+ $isList: 1
+ };
+ A.CastList.prototype = {
+ cast$1$0(_, $R) {
+ return new A.CastList(this._source, this.$ti._eval$1("@<1>")._bind$1($R)._eval$1("CastList<1,2>"));
+ },
+ get$_source() {
+ return this._source;
+ }
+ };
+ A.LateError.prototype = {
+ toString$0(_) {
+ return "LateInitializationError: " + this._message;
+ }
+ };
+ A.CodeUnits.prototype = {
+ get$length(_) {
+ return this._string.length;
+ },
+ $index(_, i) {
+ var t1 = this._string;
+ if (!(i >= 0 && i < t1.length))
+ return A.ioore(t1, i);
+ return t1.charCodeAt(i);
+ }
+ };
+ A.nullFuture_closure.prototype = {
+ call$0() {
+ return A.Future_Future$value(null, type$.Null);
+ },
+ $signature: 60
+ };
+ A.SentinelValue.prototype = {};
+ A.EfficientLengthIterable.prototype = {};
+ A.ListIterable.prototype = {
+ get$iterator(_) {
+ var _this = this;
+ return new A.ListIterator(_this, _this.get$length(_this), A._instanceType(_this)._eval$1("ListIterator<ListIterable.E>"));
+ },
+ get$isEmpty(_) {
+ return this.get$length(this) === 0;
+ },
+ join$1(_, separator) {
+ var first, t1, i, _this = this,
+ $length = _this.get$length(_this);
+ if (separator.length !== 0) {
+ if ($length === 0)
+ return "";
+ first = A.S(_this.elementAt$1(0, 0));
+ if ($length !== _this.get$length(_this))
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ for (t1 = first, i = 1; i < $length; ++i) {
+ t1 = t1 + separator + A.S(_this.elementAt$1(0, i));
+ if ($length !== _this.get$length(_this))
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ } else {
+ for (i = 0, t1 = ""; i < $length; ++i) {
+ t1 += A.S(_this.elementAt$1(0, i));
+ if ($length !== _this.get$length(_this))
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ },
+ join$0($receiver) {
+ return this.join$1($receiver, "");
+ },
+ map$1$1(_, toElement, $T) {
+ var t1 = A._instanceType(this);
+ return new A.MappedListIterable(this, t1._bind$1($T)._eval$1("1(ListIterable.E)")._as(toElement), t1._eval$1("@<ListIterable.E>")._bind$1($T)._eval$1("MappedListIterable<1,2>"));
+ },
+ fold$1$2(_, initialValue, combine, $T) {
+ var $length, value, i, _this = this;
+ $T._as(initialValue);
+ A._instanceType(_this)._bind$1($T)._eval$1("1(1,ListIterable.E)")._as(combine);
+ $length = _this.get$length(_this);
+ for (value = initialValue, i = 0; i < $length; ++i) {
+ value = combine.call$2(value, _this.elementAt$1(0, i));
+ if ($length !== _this.get$length(_this))
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ return value;
+ },
+ skip$1(_, count) {
+ return A.SubListIterable$(this, count, null, A._instanceType(this)._eval$1("ListIterable.E"));
+ }
+ };
+ A.SubListIterable.prototype = {
+ SubListIterable$3(_iterable, _start, _endOrLength, $E) {
+ var endOrLength,
+ t1 = this._start;
+ A.RangeError_checkNotNegative(t1, "start");
+ endOrLength = this._endOrLength;
+ if (endOrLength != null) {
+ A.RangeError_checkNotNegative(endOrLength, "end");
+ if (t1 > endOrLength)
+ throw A.wrapException(A.RangeError$range(t1, 0, endOrLength, "start", null));
+ }
+ },
+ get$_endIndex() {
+ var $length = J.get$length$asx(this.__internal$_iterable),
+ endOrLength = this._endOrLength;
+ if (endOrLength == null || endOrLength > $length)
+ return $length;
+ return endOrLength;
+ },
+ get$_startIndex() {
+ var $length = J.get$length$asx(this.__internal$_iterable),
+ t1 = this._start;
+ if (t1 > $length)
+ return $length;
+ return t1;
+ },
+ get$length(_) {
+ var endOrLength,
+ $length = J.get$length$asx(this.__internal$_iterable),
+ t1 = this._start;
+ if (t1 >= $length)
+ return 0;
+ endOrLength = this._endOrLength;
+ if (endOrLength == null || endOrLength >= $length)
+ return $length - t1;
+ if (typeof endOrLength !== "number")
+ return endOrLength.$sub();
+ return endOrLength - t1;
+ },
+ elementAt$1(_, index) {
+ var _this = this,
+ realIndex = _this.get$_startIndex() + index;
+ if (index < 0 || realIndex >= _this.get$_endIndex())
+ throw A.wrapException(A.IndexError$withLength(index, _this.get$length(_this), _this, "index"));
+ return J.elementAt$1$ax(_this.__internal$_iterable, realIndex);
+ },
+ skip$1(_, count) {
+ var newStart, endOrLength, _this = this;
+ A.RangeError_checkNotNegative(count, "count");
+ newStart = _this._start + count;
+ endOrLength = _this._endOrLength;
+ if (endOrLength != null && newStart >= endOrLength)
+ return new A.EmptyIterable(_this.$ti._eval$1("EmptyIterable<1>"));
+ return A.SubListIterable$(_this.__internal$_iterable, newStart, endOrLength, _this.$ti._precomputed1);
+ },
+ toList$1$growable(_, growable) {
+ var $length, result, i, _this = this,
+ start = _this._start,
+ t1 = _this.__internal$_iterable,
+ t2 = J.getInterceptor$asx(t1),
+ end = t2.get$length(t1),
+ endOrLength = _this._endOrLength;
+ if (endOrLength != null && endOrLength < end)
+ end = endOrLength;
+ $length = end - start;
+ if ($length <= 0) {
+ t1 = J.JSArray_JSArray$fixed(0, _this.$ti._precomputed1);
+ return t1;
+ }
+ result = A.List_List$filled($length, t2.elementAt$1(t1, start), false, _this.$ti._precomputed1);
+ for (i = 1; i < $length; ++i) {
+ B.JSArray_methods.$indexSet(result, i, t2.elementAt$1(t1, start + i));
+ if (t2.get$length(t1) < end)
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ return result;
+ }
+ };
+ A.ListIterator.prototype = {
+ get$current(_) {
+ var t1 = this.__internal$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var t3, _this = this,
+ t1 = _this.__internal$_iterable,
+ t2 = J.getInterceptor$asx(t1),
+ $length = t2.get$length(t1);
+ if (_this.__internal$_length !== $length)
+ throw A.wrapException(A.ConcurrentModificationError$(t1));
+ t3 = _this.__internal$_index;
+ if (t3 >= $length) {
+ _this.set$__internal$_current(null);
+ return false;
+ }
+ _this.set$__internal$_current(t2.elementAt$1(t1, t3));
+ ++_this.__internal$_index;
+ return true;
+ },
+ set$__internal$_current(_current) {
+ this.__internal$_current = this.$ti._eval$1("1?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A.MappedIterable.prototype = {
+ get$iterator(_) {
+ var t1 = A._instanceType(this);
+ return new A.MappedIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, t1._eval$1("@<1>")._bind$1(t1._rest[1])._eval$1("MappedIterator<1,2>"));
+ },
+ get$length(_) {
+ return J.get$length$asx(this.__internal$_iterable);
+ },
+ get$isEmpty(_) {
+ return J.get$isEmpty$asx(this.__internal$_iterable);
+ },
+ elementAt$1(_, index) {
+ return this._f.call$1(J.elementAt$1$ax(this.__internal$_iterable, index));
+ }
+ };
+ A.EfficientLengthMappedIterable.prototype = {$isEfficientLengthIterable: 1};
+ A.MappedIterator.prototype = {
+ moveNext$0() {
+ var _this = this,
+ t1 = _this._iterator;
+ if (t1.moveNext$0()) {
+ _this.set$__internal$_current(_this._f.call$1(t1.get$current(t1)));
+ return true;
+ }
+ _this.set$__internal$_current(null);
+ return false;
+ },
+ get$current(_) {
+ var t1 = this.__internal$_current;
+ return t1 == null ? this.$ti._rest[1]._as(t1) : t1;
+ },
+ set$__internal$_current(_current) {
+ this.__internal$_current = this.$ti._eval$1("2?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A.MappedListIterable.prototype = {
+ get$length(_) {
+ return J.get$length$asx(this._source);
+ },
+ elementAt$1(_, index) {
+ return this._f.call$1(J.elementAt$1$ax(this._source, index));
+ }
+ };
+ A.WhereIterable.prototype = {
+ get$iterator(_) {
+ return new A.WhereIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, this.$ti._eval$1("WhereIterator<1>"));
+ },
+ map$1$1(_, toElement, $T) {
+ var t1 = this.$ti;
+ return new A.MappedIterable(this, t1._bind$1($T)._eval$1("1(2)")._as(toElement), t1._eval$1("@<1>")._bind$1($T)._eval$1("MappedIterable<1,2>"));
+ }
+ };
+ A.WhereIterator.prototype = {
+ moveNext$0() {
+ var t1, t2;
+ for (t1 = this._iterator, t2 = this._f; t1.moveNext$0();)
+ if (A.boolConversionCheck(t2.call$1(t1.get$current(t1))))
+ return true;
+ return false;
+ },
+ get$current(_) {
+ var t1 = this._iterator;
+ return t1.get$current(t1);
+ },
+ $isIterator: 1
+ };
+ A.ExpandIterable.prototype = {
+ get$iterator(_) {
+ var t1 = this.$ti;
+ return new A.ExpandIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, B.C_EmptyIterator, t1._eval$1("@<1>")._bind$1(t1._rest[1])._eval$1("ExpandIterator<1,2>"));
+ }
+ };
+ A.ExpandIterator.prototype = {
+ get$current(_) {
+ var t1 = this.__internal$_current;
+ return t1 == null ? this.$ti._rest[1]._as(t1) : t1;
+ },
+ moveNext$0() {
+ var t1, t2, _this = this;
+ if (_this._currentExpansion == null)
+ return false;
+ for (t1 = _this._iterator, t2 = _this._f; !_this._currentExpansion.moveNext$0();) {
+ _this.set$__internal$_current(null);
+ if (t1.moveNext$0()) {
+ _this.set$_currentExpansion(null);
+ _this.set$_currentExpansion(J.get$iterator$ax(t2.call$1(t1.get$current(t1))));
+ } else
+ return false;
+ }
+ t1 = _this._currentExpansion;
+ _this.set$__internal$_current(t1.get$current(t1));
+ return true;
+ },
+ set$_currentExpansion(_currentExpansion) {
+ this._currentExpansion = this.$ti._eval$1("Iterator<2>?")._as(_currentExpansion);
+ },
+ set$__internal$_current(_current) {
+ this.__internal$_current = this.$ti._eval$1("2?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A.TakeIterable.prototype = {
+ get$iterator(_) {
+ return new A.TakeIterator(J.get$iterator$ax(this.__internal$_iterable), this._takeCount, A._instanceType(this)._eval$1("TakeIterator<1>"));
+ }
+ };
+ A.EfficientLengthTakeIterable.prototype = {
+ get$length(_) {
+ var iterableLength = J.get$length$asx(this.__internal$_iterable),
+ t1 = this._takeCount;
+ if (iterableLength > t1)
+ return t1;
+ return iterableLength;
+ },
+ $isEfficientLengthIterable: 1
+ };
+ A.TakeIterator.prototype = {
+ moveNext$0() {
+ if (--this._remaining >= 0)
+ return this._iterator.moveNext$0();
+ this._remaining = -1;
+ return false;
+ },
+ get$current(_) {
+ var t1;
+ if (this._remaining < 0) {
+ this.$ti._precomputed1._as(null);
+ return null;
+ }
+ t1 = this._iterator;
+ return t1.get$current(t1);
+ },
+ $isIterator: 1
+ };
+ A.SkipIterable.prototype = {
+ skip$1(_, count) {
+ A.ArgumentError_checkNotNull(count, "count", type$.int);
+ A.RangeError_checkNotNegative(count, "count");
+ return new A.SkipIterable(this.__internal$_iterable, this._skipCount + count, A._instanceType(this)._eval$1("SkipIterable<1>"));
+ },
+ get$iterator(_) {
+ return new A.SkipIterator(J.get$iterator$ax(this.__internal$_iterable), this._skipCount, A._instanceType(this)._eval$1("SkipIterator<1>"));
+ }
+ };
+ A.EfficientLengthSkipIterable.prototype = {
+ get$length(_) {
+ var $length = J.get$length$asx(this.__internal$_iterable) - this._skipCount;
+ if ($length >= 0)
+ return $length;
+ return 0;
+ },
+ skip$1(_, count) {
+ A.ArgumentError_checkNotNull(count, "count", type$.int);
+ A.RangeError_checkNotNegative(count, "count");
+ return new A.EfficientLengthSkipIterable(this.__internal$_iterable, this._skipCount + count, this.$ti);
+ },
+ $isEfficientLengthIterable: 1
+ };
+ A.SkipIterator.prototype = {
+ moveNext$0() {
+ var t1, i;
+ for (t1 = this._iterator, i = 0; i < this._skipCount; ++i)
+ t1.moveNext$0();
+ this._skipCount = 0;
+ return t1.moveNext$0();
+ },
+ get$current(_) {
+ var t1 = this._iterator;
+ return t1.get$current(t1);
+ },
+ $isIterator: 1
+ };
+ A.SkipWhileIterable.prototype = {
+ get$iterator(_) {
+ return new A.SkipWhileIterator(J.get$iterator$ax(this.__internal$_iterable), this._f, this.$ti._eval$1("SkipWhileIterator<1>"));
+ }
+ };
+ A.SkipWhileIterator.prototype = {
+ moveNext$0() {
+ var t1, t2, _this = this;
+ if (!_this._hasSkipped) {
+ _this._hasSkipped = true;
+ for (t1 = _this._iterator, t2 = _this._f; t1.moveNext$0();)
+ if (!A.boolConversionCheck(t2.call$1(t1.get$current(t1))))
+ return true;
+ }
+ return _this._iterator.moveNext$0();
+ },
+ get$current(_) {
+ var t1 = this._iterator;
+ return t1.get$current(t1);
+ },
+ $isIterator: 1
+ };
+ A.EmptyIterable.prototype = {
+ get$iterator(_) {
+ return B.C_EmptyIterator;
+ },
+ get$isEmpty(_) {
+ return true;
+ },
+ get$length(_) {
+ return 0;
+ },
+ elementAt$1(_, index) {
+ throw A.wrapException(A.RangeError$range(index, 0, 0, "index", null));
+ },
+ map$1$1(_, toElement, $T) {
+ this.$ti._bind$1($T)._eval$1("1(2)")._as(toElement);
+ return new A.EmptyIterable($T._eval$1("EmptyIterable<0>"));
+ },
+ skip$1(_, count) {
+ A.RangeError_checkNotNegative(count, "count");
+ return this;
+ }
+ };
+ A.EmptyIterator.prototype = {
+ moveNext$0() {
+ return false;
+ },
+ get$current(_) {
+ throw A.wrapException(A.IterableElementError_noElement());
+ },
+ $isIterator: 1
+ };
+ A.WhereTypeIterable.prototype = {
+ get$iterator(_) {
+ return new A.WhereTypeIterator(J.get$iterator$ax(this._source), this.$ti._eval$1("WhereTypeIterator<1>"));
+ }
+ };
+ A.WhereTypeIterator.prototype = {
+ moveNext$0() {
+ var t1, t2;
+ for (t1 = this._source, t2 = this.$ti._precomputed1; t1.moveNext$0();)
+ if (t2._is(t1.get$current(t1)))
+ return true;
+ return false;
+ },
+ get$current(_) {
+ var t1 = this._source;
+ return this.$ti._precomputed1._as(t1.get$current(t1));
+ },
+ $isIterator: 1
+ };
+ A.FixedLengthListMixin.prototype = {};
+ A.UnmodifiableListMixin.prototype = {
+ $indexSet(_, index, value) {
+ A._instanceType(this)._eval$1("UnmodifiableListMixin.E")._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot modify an unmodifiable list"));
+ }
+ };
+ A.UnmodifiableListBase.prototype = {};
+ A.ReversedListIterable.prototype = {
+ get$length(_) {
+ return J.get$length$asx(this._source);
+ },
+ elementAt$1(_, index) {
+ var t1 = this._source,
+ t2 = J.getInterceptor$asx(t1);
+ return t2.elementAt$1(t1, t2.get$length(t1) - 1 - index);
+ }
+ };
+ A.Symbol.prototype = {
+ get$hashCode(_) {
+ var hash = this._hashCode;
+ if (hash != null)
+ return hash;
+ hash = 664597 * B.JSString_methods.get$hashCode(this._name) & 536870911;
+ this._hashCode = hash;
+ return hash;
+ },
+ toString$0(_) {
+ return 'Symbol("' + this._name + '")';
+ },
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.Symbol && this._name === other._name;
+ },
+ $isSymbol0: 1
+ };
+ A.__CastListBase__CastIterableBase_ListMixin.prototype = {};
+ A.ConstantMapView.prototype = {};
+ A.ConstantMap.prototype = {
+ get$isEmpty(_) {
+ return this.get$length(this) === 0;
+ },
+ toString$0(_) {
+ return A.MapBase_mapToString(this);
+ },
+ $indexSet(_, key, value) {
+ var t1 = A._instanceType(this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ A.ConstantMap__throwUnmodifiable();
+ },
+ $isMap: 1
+ };
+ A.ConstantStringMap.prototype = {
+ get$length(_) {
+ return this._values.length;
+ },
+ get$__js_helper$_keys() {
+ var keys = this.$keys;
+ if (keys == null) {
+ keys = Object.keys(this._jsIndex);
+ this.$keys = keys;
+ }
+ return keys;
+ },
+ containsKey$1(_, key) {
+ if (typeof key != "string")
+ return false;
+ if ("__proto__" === key)
+ return false;
+ return this._jsIndex.hasOwnProperty(key);
+ },
+ $index(_, key) {
+ if (!this.containsKey$1(0, key))
+ return null;
+ return this._values[this._jsIndex[key]];
+ },
+ forEach$1(_, f) {
+ var keys, values, t1, i;
+ this.$ti._eval$1("~(1,2)")._as(f);
+ keys = this.get$__js_helper$_keys();
+ values = this._values;
+ for (t1 = keys.length, i = 0; i < t1; ++i)
+ f.call$2(keys[i], values[i]);
+ },
+ get$keys(_) {
+ return new A._KeysOrValues(this.get$__js_helper$_keys(), this.$ti._eval$1("_KeysOrValues<1>"));
+ }
+ };
+ A._KeysOrValues.prototype = {
+ get$length(_) {
+ return this._elements.length;
+ },
+ get$isEmpty(_) {
+ return 0 === this._elements.length;
+ },
+ get$isNotEmpty(_) {
+ return 0 !== this._elements.length;
+ },
+ get$iterator(_) {
+ var t1 = this._elements;
+ return new A._KeysOrValuesOrElementsIterator(t1, t1.length, this.$ti._eval$1("_KeysOrValuesOrElementsIterator<1>"));
+ }
+ };
+ A._KeysOrValuesOrElementsIterator.prototype = {
+ get$current(_) {
+ var t1 = this.__js_helper$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var _this = this,
+ t1 = _this.__js_helper$_index;
+ if (t1 >= _this._length) {
+ _this.set$__js_helper$_current(null);
+ return false;
+ }
+ _this.set$__js_helper$_current(_this._elements[t1]);
+ ++_this.__js_helper$_index;
+ return true;
+ },
+ set$__js_helper$_current(_current) {
+ this.__js_helper$_current = this.$ti._eval$1("1?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A.Instantiation.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.Instantiation1 && this._genericClosure.$eq(0, other._genericClosure) && A.getRuntimeTypeOfClosure(this) === A.getRuntimeTypeOfClosure(other);
+ },
+ get$hashCode(_) {
+ return A.Object_hash(this._genericClosure, A.getRuntimeTypeOfClosure(this), B.C_SentinelValue, B.C_SentinelValue);
+ },
+ toString$0(_) {
+ var t1 = B.JSArray_methods.join$1([A.createRuntimeType(this.$ti._precomputed1)], ", ");
+ return this._genericClosure.toString$0(0) + " with " + ("<" + t1 + ">");
+ }
+ };
+ A.Instantiation1.prototype = {
+ call$2(a0, a1) {
+ return this._genericClosure.call$1$2(a0, a1, this.$ti._rest[0]);
+ },
+ call$4(a0, a1, a2, a3) {
+ return this._genericClosure.call$1$4(a0, a1, a2, a3, this.$ti._rest[0]);
+ },
+ $signature() {
+ return A.instantiatedGenericFunctionType(A.closureFunctionType(this._genericClosure), this.$ti);
+ }
+ };
+ A.JSInvocationMirror.prototype = {
+ get$memberName() {
+ var t1 = this._memberName;
+ return t1;
+ },
+ get$positionalArguments() {
+ var t1, argumentCount, list, index, _this = this;
+ if (_this.__js_helper$_kind === 1)
+ return B.List_empty0;
+ t1 = _this._arguments;
+ argumentCount = t1.length - _this._namedArgumentNames.length - _this._typeArgumentCount;
+ if (argumentCount === 0)
+ return B.List_empty0;
+ list = [];
+ for (index = 0; index < argumentCount; ++index) {
+ if (!(index < t1.length))
+ return A.ioore(t1, index);
+ list.push(t1[index]);
+ }
+ return J.JSArray_markUnmodifiableList(list);
+ },
+ get$namedArguments() {
+ var t1, namedArgumentCount, t2, namedArgumentsStartIndex, map, i, t3, t4, _this = this;
+ if (_this.__js_helper$_kind !== 0)
+ return B.Map_empty0;
+ t1 = _this._namedArgumentNames;
+ namedArgumentCount = t1.length;
+ t2 = _this._arguments;
+ namedArgumentsStartIndex = t2.length - namedArgumentCount - _this._typeArgumentCount;
+ if (namedArgumentCount === 0)
+ return B.Map_empty0;
+ map = new A.JsLinkedHashMap(type$.JsLinkedHashMap_Symbol_dynamic);
+ for (i = 0; i < namedArgumentCount; ++i) {
+ if (!(i < t1.length))
+ return A.ioore(t1, i);
+ t3 = t1[i];
+ t4 = namedArgumentsStartIndex + i;
+ if (!(t4 >= 0 && t4 < t2.length))
+ return A.ioore(t2, t4);
+ map.$indexSet(0, new A.Symbol(t3), t2[t4]);
+ }
+ return new A.ConstantMapView(map, type$.ConstantMapView_Symbol_dynamic);
+ },
+ $isInvocation: 1
+ };
+ A.Primitives_functionNoSuchMethod_closure.prototype = {
+ call$2($name, argument) {
+ var t1;
+ A._asString($name);
+ t1 = this._box_0;
+ t1.names = t1.names + "$" + $name;
+ B.JSArray_methods.add$1(this.namedArgumentList, $name);
+ B.JSArray_methods.add$1(this.$arguments, argument);
+ ++t1.argumentCount;
+ },
+ $signature: 4
+ };
+ A.TypeErrorDecoder.prototype = {
+ matchTypeError$1(message) {
+ var result, t1, _this = this,
+ match = new RegExp(_this._pattern).exec(message);
+ if (match == null)
+ return null;
+ result = Object.create(null);
+ t1 = _this._arguments;
+ if (t1 !== -1)
+ result.arguments = match[t1 + 1];
+ t1 = _this._argumentsExpr;
+ if (t1 !== -1)
+ result.argumentsExpr = match[t1 + 1];
+ t1 = _this._expr;
+ if (t1 !== -1)
+ result.expr = match[t1 + 1];
+ t1 = _this._method;
+ if (t1 !== -1)
+ result.method = match[t1 + 1];
+ t1 = _this._receiver;
+ if (t1 !== -1)
+ result.receiver = match[t1 + 1];
+ return result;
+ }
+ };
+ A.NullError.prototype = {
+ toString$0(_) {
+ return "Null check operator used on a null value";
+ }
+ };
+ A.JsNoSuchMethodError.prototype = {
+ toString$0(_) {
+ var t2, _this = this,
+ _s38_ = "NoSuchMethodError: method not found: '",
+ t1 = _this._method;
+ if (t1 == null)
+ return "NoSuchMethodError: " + _this.__js_helper$_message;
+ t2 = _this._receiver;
+ if (t2 == null)
+ return _s38_ + t1 + "' (" + _this.__js_helper$_message + ")";
+ return _s38_ + t1 + "' on '" + t2 + "' (" + _this.__js_helper$_message + ")";
+ }
+ };
+ A.UnknownJsTypeError.prototype = {
+ toString$0(_) {
+ var t1 = this.__js_helper$_message;
+ return t1.length === 0 ? "Error" : "Error: " + t1;
+ }
+ };
+ A.NullThrownFromJavaScriptException.prototype = {
+ toString$0(_) {
+ return "Throw of null ('" + (this._irritant === null ? "null" : "undefined") + "' from JavaScript)";
+ },
+ $isException: 1
+ };
+ A._StackTrace.prototype = {
+ toString$0(_) {
+ var trace,
+ t1 = this._trace;
+ if (t1 != null)
+ return t1;
+ t1 = this._exception;
+ trace = t1 !== null && typeof t1 === "object" ? t1.stack : null;
+ return this._trace = trace == null ? "" : trace;
+ },
+ $isStackTrace: 1
+ };
+ A.Closure.prototype = {
+ toString$0(_) {
+ var $constructor = this.constructor,
+ $name = $constructor == null ? null : $constructor.name;
+ return "Closure '" + A.unminifyOrTag($name == null ? "unknown" : $name) + "'";
+ },
+ $isFunction: 1,
+ get$$call() {
+ return this;
+ },
+ "call*": "call$1",
+ $requiredArgCount: 1,
+ $defaultValues: null
+ };
+ A.Closure0Args.prototype = {"call*": "call$0", $requiredArgCount: 0};
+ A.Closure2Args.prototype = {"call*": "call$2", $requiredArgCount: 2};
+ A.TearOffClosure.prototype = {};
+ A.StaticClosure.prototype = {
+ toString$0(_) {
+ var $name = this.$static_name;
+ if ($name == null)
+ return "Closure of unknown static method";
+ return "Closure '" + A.unminifyOrTag($name) + "'";
+ }
+ };
+ A.BoundClosure.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ if (this === other)
+ return true;
+ if (!(other instanceof A.BoundClosure))
+ return false;
+ return this.$_target === other.$_target && this._receiver === other._receiver;
+ },
+ get$hashCode(_) {
+ return (A.objectHashCode(this._receiver) ^ A.Primitives_objectHashCode(this.$_target)) >>> 0;
+ },
+ toString$0(_) {
+ return "Closure '" + this.$_name + "' of " + ("Instance of '" + A.Primitives_objectTypeName(this._receiver) + "'");
+ }
+ };
+ A._CyclicInitializationError.prototype = {
+ toString$0(_) {
+ return "Reading static variable '" + this.variableName + "' during its initialization";
+ }
+ };
+ A.RuntimeError.prototype = {
+ toString$0(_) {
+ return "RuntimeError: " + this.message;
+ }
+ };
+ A._AssertionError.prototype = {
+ toString$0(_) {
+ return "Assertion failed: " + A.Error_safeToString(this.message);
+ }
+ };
+ A._Required.prototype = {};
+ A.JsLinkedHashMap.prototype = {
+ get$length(_) {
+ return this._length;
+ },
+ get$isEmpty(_) {
+ return this._length === 0;
+ },
+ get$keys(_) {
+ return new A.LinkedHashMapKeyIterable(this, A._instanceType(this)._eval$1("LinkedHashMapKeyIterable<1>"));
+ },
+ get$values(_) {
+ var t1 = A._instanceType(this);
+ return A.MappedIterable_MappedIterable(new A.LinkedHashMapKeyIterable(this, t1._eval$1("LinkedHashMapKeyIterable<1>")), new A.JsLinkedHashMap_values_closure(this), t1._precomputed1, t1._rest[1]);
+ },
+ containsKey$1(_, key) {
+ var strings, nums;
+ if (typeof key == "string") {
+ strings = this._strings;
+ if (strings == null)
+ return false;
+ return strings[key] != null;
+ } else if (typeof key == "number" && (key & 0x3fffffff) === key) {
+ nums = this._nums;
+ if (nums == null)
+ return false;
+ return nums[key] != null;
+ } else
+ return this.internalContainsKey$1(key);
+ },
+ internalContainsKey$1(key) {
+ var rest = this.__js_helper$_rest;
+ if (rest == null)
+ return false;
+ return this.internalFindBucketIndex$2(rest[this.internalComputeHashCode$1(key)], key) >= 0;
+ },
+ $index(_, key) {
+ var strings, cell, t1, nums, _null = null;
+ if (typeof key == "string") {
+ strings = this._strings;
+ if (strings == null)
+ return _null;
+ cell = strings[key];
+ t1 = cell == null ? _null : cell.hashMapCellValue;
+ return t1;
+ } else if (typeof key == "number" && (key & 0x3fffffff) === key) {
+ nums = this._nums;
+ if (nums == null)
+ return _null;
+ cell = nums[key];
+ t1 = cell == null ? _null : cell.hashMapCellValue;
+ return t1;
+ } else
+ return this.internalGet$1(key);
+ },
+ internalGet$1(key) {
+ var bucket, index,
+ rest = this.__js_helper$_rest;
+ if (rest == null)
+ return null;
+ bucket = rest[this.internalComputeHashCode$1(key)];
+ index = this.internalFindBucketIndex$2(bucket, key);
+ if (index < 0)
+ return null;
+ return bucket[index].hashMapCellValue;
+ },
+ $indexSet(_, key, value) {
+ var strings, nums, rest, hash, bucket, index, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ if (typeof key == "string") {
+ strings = _this._strings;
+ _this._addHashTableEntry$3(strings == null ? _this._strings = _this._newHashTable$0() : strings, key, value);
+ } else if (typeof key == "number" && (key & 0x3fffffff) === key) {
+ nums = _this._nums;
+ _this._addHashTableEntry$3(nums == null ? _this._nums = _this._newHashTable$0() : nums, key, value);
+ } else {
+ rest = _this.__js_helper$_rest;
+ if (rest == null)
+ rest = _this.__js_helper$_rest = _this._newHashTable$0();
+ hash = _this.internalComputeHashCode$1(key);
+ bucket = rest[hash];
+ if (bucket == null)
+ rest[hash] = [_this._newLinkedCell$2(key, value)];
+ else {
+ index = _this.internalFindBucketIndex$2(bucket, key);
+ if (index >= 0)
+ bucket[index].hashMapCellValue = value;
+ else
+ bucket.push(_this._newLinkedCell$2(key, value));
+ }
+ }
+ },
+ putIfAbsent$2(_, key, ifAbsent) {
+ var t2, value, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(key);
+ t1._eval$1("2()")._as(ifAbsent);
+ if (_this.containsKey$1(0, key)) {
+ t2 = _this.$index(0, key);
+ return t2 == null ? t1._rest[1]._as(t2) : t2;
+ }
+ value = ifAbsent.call$0();
+ _this.$indexSet(0, key, value);
+ return value;
+ },
+ remove$1(_, key) {
+ var _this = this;
+ if (typeof key == "string")
+ return _this._removeHashTableEntry$2(_this._strings, key);
+ else if (typeof key == "number" && (key & 0x3fffffff) === key)
+ return _this._removeHashTableEntry$2(_this._nums, key);
+ else
+ return _this.internalRemove$1(key);
+ },
+ internalRemove$1(key) {
+ var hash, bucket, index, cell, _this = this,
+ rest = _this.__js_helper$_rest;
+ if (rest == null)
+ return null;
+ hash = _this.internalComputeHashCode$1(key);
+ bucket = rest[hash];
+ index = _this.internalFindBucketIndex$2(bucket, key);
+ if (index < 0)
+ return null;
+ cell = bucket.splice(index, 1)[0];
+ _this._unlinkCell$1(cell);
+ if (bucket.length === 0)
+ delete rest[hash];
+ return cell.hashMapCellValue;
+ },
+ clear$0(_) {
+ var _this = this;
+ if (_this._length > 0) {
+ _this._strings = _this._nums = _this.__js_helper$_rest = _this._first = _this._last = null;
+ _this._length = 0;
+ _this._modified$0();
+ }
+ },
+ forEach$1(_, action) {
+ var cell, modifications, _this = this;
+ A._instanceType(_this)._eval$1("~(1,2)")._as(action);
+ cell = _this._first;
+ modifications = _this._modifications;
+ for (; cell != null;) {
+ action.call$2(cell.hashMapCellKey, cell.hashMapCellValue);
+ if (modifications !== _this._modifications)
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ cell = cell._next;
+ }
+ },
+ _addHashTableEntry$3(table, key, value) {
+ var cell,
+ t1 = A._instanceType(this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ cell = table[key];
+ if (cell == null)
+ table[key] = this._newLinkedCell$2(key, value);
+ else
+ cell.hashMapCellValue = value;
+ },
+ _removeHashTableEntry$2(table, key) {
+ var cell;
+ if (table == null)
+ return null;
+ cell = table[key];
+ if (cell == null)
+ return null;
+ this._unlinkCell$1(cell);
+ delete table[key];
+ return cell.hashMapCellValue;
+ },
+ _modified$0() {
+ this._modifications = this._modifications + 1 & 1073741823;
+ },
+ _newLinkedCell$2(key, value) {
+ var _this = this,
+ t1 = A._instanceType(_this),
+ cell = new A.LinkedHashMapCell(t1._precomputed1._as(key), t1._rest[1]._as(value));
+ if (_this._first == null)
+ _this._first = _this._last = cell;
+ else {
+ t1 = _this._last;
+ t1.toString;
+ cell._previous = t1;
+ _this._last = t1._next = cell;
+ }
+ ++_this._length;
+ _this._modified$0();
+ return cell;
+ },
+ _unlinkCell$1(cell) {
+ var _this = this,
+ previous = cell._previous,
+ next = cell._next;
+ if (previous == null)
+ _this._first = next;
+ else
+ previous._next = next;
+ if (next == null)
+ _this._last = previous;
+ else
+ next._previous = previous;
+ --_this._length;
+ _this._modified$0();
+ },
+ internalComputeHashCode$1(key) {
+ return J.get$hashCode$(key) & 1073741823;
+ },
+ internalFindBucketIndex$2(bucket, key) {
+ var $length, i;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; ++i)
+ if (J.$eq$(bucket[i].hashMapCellKey, key))
+ return i;
+ return -1;
+ },
+ toString$0(_) {
+ return A.MapBase_mapToString(this);
+ },
+ _newHashTable$0() {
+ var table = Object.create(null);
+ table["<non-identifier-key>"] = table;
+ delete table["<non-identifier-key>"];
+ return table;
+ },
+ $isLinkedHashMap: 1
+ };
+ A.JsLinkedHashMap_values_closure.prototype = {
+ call$1(each) {
+ var t1 = this.$this,
+ t2 = A._instanceType(t1);
+ t1 = t1.$index(0, t2._precomputed1._as(each));
+ return t1 == null ? t2._rest[1]._as(t1) : t1;
+ },
+ $signature() {
+ return A._instanceType(this.$this)._eval$1("2(1)");
+ }
+ };
+ A.LinkedHashMapCell.prototype = {};
+ A.LinkedHashMapKeyIterable.prototype = {
+ get$length(_) {
+ return this._map._length;
+ },
+ get$isEmpty(_) {
+ return this._map._length === 0;
+ },
+ get$iterator(_) {
+ var t1 = this._map,
+ t2 = new A.LinkedHashMapKeyIterator(t1, t1._modifications, this.$ti._eval$1("LinkedHashMapKeyIterator<1>"));
+ t2._cell = t1._first;
+ return t2;
+ },
+ contains$1(_, element) {
+ return this._map.containsKey$1(0, element);
+ }
+ };
+ A.LinkedHashMapKeyIterator.prototype = {
+ get$current(_) {
+ return this.__js_helper$_current;
+ },
+ moveNext$0() {
+ var cell, _this = this,
+ t1 = _this._map;
+ if (_this._modifications !== t1._modifications)
+ throw A.wrapException(A.ConcurrentModificationError$(t1));
+ cell = _this._cell;
+ if (cell == null) {
+ _this.set$__js_helper$_current(null);
+ return false;
+ } else {
+ _this.set$__js_helper$_current(cell.hashMapCellKey);
+ _this._cell = cell._next;
+ return true;
+ }
+ },
+ set$__js_helper$_current(_current) {
+ this.__js_helper$_current = this.$ti._eval$1("1?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A.initHooks_closure.prototype = {
+ call$1(o) {
+ return this.getTag(o);
+ },
+ $signature: 15
+ };
+ A.initHooks_closure0.prototype = {
+ call$2(o, tag) {
+ return this.getUnknownTag(o, tag);
+ },
+ $signature: 36
+ };
+ A.initHooks_closure1.prototype = {
+ call$1(tag) {
+ return this.prototypeForTag(A._asString(tag));
+ },
+ $signature: 37
+ };
+ A.JSSyntaxRegExp.prototype = {
+ toString$0(_) {
+ return "RegExp/" + this.pattern + "/" + this._nativeRegExp.flags;
+ },
+ get$_nativeGlobalVersion() {
+ var _this = this,
+ t1 = _this._nativeGlobalRegExp;
+ if (t1 != null)
+ return t1;
+ t1 = _this._nativeRegExp;
+ return _this._nativeGlobalRegExp = A.JSSyntaxRegExp_makeNative(_this.pattern, t1.multiline, !t1.ignoreCase, t1.unicode, t1.dotAll, true);
+ },
+ get$_nativeAnchoredVersion() {
+ var _this = this,
+ t1 = _this._nativeAnchoredRegExp;
+ if (t1 != null)
+ return t1;
+ t1 = _this._nativeRegExp;
+ return _this._nativeAnchoredRegExp = A.JSSyntaxRegExp_makeNative(_this.pattern + "|()", t1.multiline, !t1.ignoreCase, t1.unicode, t1.dotAll, true);
+ },
+ firstMatch$1(string) {
+ var m = this._nativeRegExp.exec(string);
+ if (m == null)
+ return null;
+ return new A._MatchImplementation(m);
+ },
+ allMatches$2(_, string, start) {
+ var t1 = string.length;
+ if (start > t1)
+ throw A.wrapException(A.RangeError$range(start, 0, t1, null, null));
+ return new A._AllMatchesIterable(this, string, start);
+ },
+ allMatches$1($receiver, string) {
+ return this.allMatches$2($receiver, string, 0);
+ },
+ _execGlobal$2(string, start) {
+ var match,
+ regexp = this.get$_nativeGlobalVersion();
+ if (regexp == null)
+ regexp = type$.Object._as(regexp);
+ regexp.lastIndex = start;
+ match = regexp.exec(string);
+ if (match == null)
+ return null;
+ return new A._MatchImplementation(match);
+ },
+ _execAnchored$2(string, start) {
+ var match,
+ regexp = this.get$_nativeAnchoredVersion();
+ if (regexp == null)
+ regexp = type$.Object._as(regexp);
+ regexp.lastIndex = start;
+ match = regexp.exec(string);
+ if (match == null)
+ return null;
+ if (0 >= match.length)
+ return A.ioore(match, -1);
+ if (match.pop() != null)
+ return null;
+ return new A._MatchImplementation(match);
+ },
+ matchAsPrefix$2(_, string, start) {
+ if (start < 0 || start > string.length)
+ throw A.wrapException(A.RangeError$range(start, 0, string.length, null, null));
+ return this._execAnchored$2(string, start);
+ },
+ $isPattern: 1,
+ $isRegExp: 1
+ };
+ A._MatchImplementation.prototype = {
+ get$start(_) {
+ return this._match.index;
+ },
+ get$end(_) {
+ var t1 = this._match;
+ return t1.index + t1[0].length;
+ },
+ $isMatch: 1,
+ $isRegExpMatch: 1
+ };
+ A._AllMatchesIterable.prototype = {
+ get$iterator(_) {
+ return new A._AllMatchesIterator(this._re, this.__js_helper$_string, this.__js_helper$_start);
+ }
+ };
+ A._AllMatchesIterator.prototype = {
+ get$current(_) {
+ var t1 = this.__js_helper$_current;
+ return t1 == null ? type$.RegExpMatch._as(t1) : t1;
+ },
+ moveNext$0() {
+ var t1, t2, t3, match, nextIndex, _this = this,
+ string = _this.__js_helper$_string;
+ if (string == null)
+ return false;
+ t1 = _this._nextIndex;
+ t2 = string.length;
+ if (t1 <= t2) {
+ t3 = _this._regExp;
+ match = t3._execGlobal$2(string, t1);
+ if (match != null) {
+ _this.__js_helper$_current = match;
+ nextIndex = match.get$end(match);
+ if (match._match.index === nextIndex) {
+ if (t3._nativeRegExp.unicode) {
+ t1 = _this._nextIndex;
+ t3 = t1 + 1;
+ if (t3 < t2) {
+ if (!(t1 >= 0 && t1 < t2))
+ return A.ioore(string, t1);
+ t1 = string.charCodeAt(t1);
+ if (t1 >= 55296 && t1 <= 56319) {
+ if (!(t3 >= 0))
+ return A.ioore(string, t3);
+ t1 = string.charCodeAt(t3);
+ t1 = t1 >= 56320 && t1 <= 57343;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ nextIndex = (t1 ? nextIndex + 1 : nextIndex) + 1;
+ }
+ _this._nextIndex = nextIndex;
+ return true;
+ }
+ }
+ _this.__js_helper$_string = _this.__js_helper$_current = null;
+ return false;
+ },
+ $isIterator: 1
+ };
+ A.StringMatch.prototype = {
+ get$end(_) {
+ return this.start + this.pattern.length;
+ },
+ $isMatch: 1,
+ get$start(receiver) {
+ return this.start;
+ }
+ };
+ A._StringAllMatchesIterable.prototype = {
+ get$iterator(_) {
+ return new A._StringAllMatchesIterator(this._input, this._pattern, this.__js_helper$_index);
+ }
+ };
+ A._StringAllMatchesIterator.prototype = {
+ moveNext$0() {
+ var index, end, _this = this,
+ t1 = _this.__js_helper$_index,
+ t2 = _this._pattern,
+ t3 = t2.length,
+ t4 = _this._input,
+ t5 = t4.length;
+ if (t1 + t3 > t5) {
+ _this.__js_helper$_current = null;
+ return false;
+ }
+ index = t4.indexOf(t2, t1);
+ if (index < 0) {
+ _this.__js_helper$_index = t5 + 1;
+ _this.__js_helper$_current = null;
+ return false;
+ }
+ end = index + t3;
+ _this.__js_helper$_current = new A.StringMatch(index, t2);
+ _this.__js_helper$_index = end === _this.__js_helper$_index ? end + 1 : end;
+ return true;
+ },
+ get$current(_) {
+ var t1 = this.__js_helper$_current;
+ t1.toString;
+ return t1;
+ },
+ $isIterator: 1
+ };
+ A._Cell.prototype = {
+ _readLocal$0() {
+ var t1 = this._value;
+ if (t1 === this)
+ throw A.wrapException(new A.LateError("Local '" + this.__late_helper$_name + "' has not been initialized."));
+ return t1;
+ }
+ };
+ A._InitializedCell.prototype = {
+ _readFinal$0() {
+ var result, _this = this,
+ t1 = _this._value;
+ if (t1 === _this) {
+ result = _this._initializer.call$0();
+ if (_this._value !== _this)
+ throw A.wrapException(new A.LateError("Local '" + _this.__late_helper$_name + string$.x27_has_));
+ _this._value = result;
+ t1 = result;
+ }
+ return t1;
+ }
+ };
+ A.NativeByteBuffer.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_ByteBuffer_RkP;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isByteBuffer: 1
+ };
+ A.NativeTypedData.prototype = {};
+ A.NativeByteData.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_ByteData_zNC;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isByteData: 1
+ };
+ A.NativeTypedArray.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $isJavaScriptIndexingBehavior: 1
+ };
+ A.NativeTypedArrayOfDouble.prototype = {
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $indexSet(receiver, index, value) {
+ A._asDouble(value);
+ A._checkValidIndex(index, receiver, receiver.length);
+ receiver[index] = value;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.NativeTypedArrayOfInt.prototype = {
+ $indexSet(receiver, index, value) {
+ A._asInt(value);
+ A._checkValidIndex(index, receiver, receiver.length);
+ receiver[index] = value;
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.NativeFloat32List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Float32List_LB7;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isFloat32List: 1
+ };
+ A.NativeFloat64List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Float64List_LB7;
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isFloat64List: 1
+ };
+ A.NativeInt16List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Int16List_uXf;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isInt16List: 1
+ };
+ A.NativeInt32List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Int32List_O50;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isInt32List: 1
+ };
+ A.NativeInt8List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Int8List_ekJ;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isInt8List: 1
+ };
+ A.NativeUint16List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint16List_2bx;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isUint16List: 1
+ };
+ A.NativeUint32List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint32List_2bx;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isUint32List: 1
+ };
+ A.NativeUint8ClampedList.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint8ClampedList_Jik;
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isUint8ClampedList: 1
+ };
+ A.NativeUint8List.prototype = {
+ get$runtimeType(receiver) {
+ return B.Type_Uint8List_WLA;
+ },
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $index(receiver, index) {
+ A._checkValidIndex(index, receiver, receiver.length);
+ return receiver[index];
+ },
+ sublist$2(receiver, start, end) {
+ return new Uint8Array(receiver.subarray(start, A._checkValidRange(start, end, receiver.length)));
+ },
+ $isTrustedGetRuntimeType: 1,
+ $isNativeUint8List: 1,
+ $isUint8List: 1
+ };
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin.prototype = {};
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin.prototype = {};
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin.prototype = {};
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin.prototype = {};
+ A.Rti.prototype = {
+ _eval$1(recipe) {
+ return A._Universe_evalInEnvironment(init.typeUniverse, this, recipe);
+ },
+ _bind$1(typeOrTuple) {
+ return A._Universe_bind(init.typeUniverse, this, typeOrTuple);
+ }
+ };
+ A._FunctionParameters.prototype = {};
+ A._Type.prototype = {
+ toString$0(_) {
+ return A._rtiToString(this._rti, null);
+ }
+ };
+ A._Error.prototype = {
+ toString$0(_) {
+ return this.__rti$_message;
+ }
+ };
+ A._TypeError.prototype = {$isTypeError: 1};
+ A._AsyncRun__initializeScheduleImmediate_internalCallback.prototype = {
+ call$1(_) {
+ var t1 = this._box_0,
+ f = t1.storedCallback;
+ t1.storedCallback = null;
+ f.call$0();
+ },
+ $signature: 10
+ };
+ A._AsyncRun__initializeScheduleImmediate_closure.prototype = {
+ call$1(callback) {
+ var t1, t2;
+ this._box_0.storedCallback = type$.void_Function._as(callback);
+ t1 = this.div;
+ t2 = this.span;
+ t1.firstChild ? t1.removeChild(t2) : t1.appendChild(t2);
+ },
+ $signature: 30
+ };
+ A._AsyncRun__scheduleImmediateJsOverride_internalCallback.prototype = {
+ call$0() {
+ this.callback.call$0();
+ },
+ $signature: 3
+ };
+ A._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback.prototype = {
+ call$0() {
+ this.callback.call$0();
+ },
+ $signature: 3
+ };
+ A._TimerImpl.prototype = {
+ _TimerImpl$2(milliseconds, callback) {
+ if (self.setTimeout != null)
+ self.setTimeout(A.convertDartClosureToJS(new A._TimerImpl_internalCallback(this, callback), 0), milliseconds);
+ else
+ throw A.wrapException(A.UnsupportedError$("`setTimeout()` not found."));
+ },
+ _TimerImpl$periodic$2(milliseconds, callback) {
+ if (self.setTimeout != null)
+ self.setInterval(A.convertDartClosureToJS(new A._TimerImpl$periodic_closure(this, milliseconds, Date.now(), callback), 0), milliseconds);
+ else
+ throw A.wrapException(A.UnsupportedError$("Periodic timer."));
+ },
+ $isTimer: 1
+ };
+ A._TimerImpl_internalCallback.prototype = {
+ call$0() {
+ this.$this._tick = 1;
+ this.callback.call$0();
+ },
+ $signature: 0
+ };
+ A._TimerImpl$periodic_closure.prototype = {
+ call$0() {
+ var duration, _this = this,
+ t1 = _this.$this,
+ tick = t1._tick + 1,
+ t2 = _this.milliseconds;
+ if (t2 > 0) {
+ duration = Date.now() - _this.start;
+ if (duration > (tick + 1) * t2)
+ tick = B.JSInt_methods.$tdiv(duration, t2);
+ }
+ t1._tick = tick;
+ _this.callback.call$1(t1);
+ },
+ $signature: 3
+ };
+ A.AsyncError.prototype = {
+ toString$0(_) {
+ return A.S(this.error);
+ },
+ $isError: 1,
+ get$stackTrace() {
+ return this.stackTrace;
+ }
+ };
+ A._Completer.prototype = {
+ completeError$2(error, stackTrace) {
+ var replacement;
+ A.checkNotNullable(error, "error", type$.Object);
+ if ((this.future._async$_state & 30) !== 0)
+ throw A.wrapException(A.StateError$("Future already completed"));
+ replacement = $.Zone__current.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ error = replacement.error;
+ stackTrace = replacement.stackTrace;
+ } else if (stackTrace == null)
+ stackTrace = A.AsyncError_defaultStackTrace(error);
+ this._completeError$2(error, stackTrace);
+ },
+ completeError$1(error) {
+ return this.completeError$2(error, null);
+ },
+ $isCompleter: 1
+ };
+ A._AsyncCompleter.prototype = {
+ complete$1(_, value) {
+ var t2,
+ t1 = this.$ti;
+ t1._eval$1("1/?")._as(value);
+ t2 = this.future;
+ if ((t2._async$_state & 30) !== 0)
+ throw A.wrapException(A.StateError$("Future already completed"));
+ t2._asyncComplete$1(t1._eval$1("1/")._as(value));
+ },
+ complete$0($receiver) {
+ return this.complete$1($receiver, null);
+ },
+ _completeError$2(error, stackTrace) {
+ this.future._asyncCompleteError$2(error, stackTrace);
+ }
+ };
+ A._SyncCompleter.prototype = {
+ complete$1(_, value) {
+ var t2,
+ t1 = this.$ti;
+ t1._eval$1("1/?")._as(value);
+ t2 = this.future;
+ if ((t2._async$_state & 30) !== 0)
+ throw A.wrapException(A.StateError$("Future already completed"));
+ t2._complete$1(t1._eval$1("1/")._as(value));
+ },
+ complete$0($receiver) {
+ return this.complete$1($receiver, null);
+ },
+ _completeError$2(error, stackTrace) {
+ this.future._completeError$2(error, stackTrace);
+ }
+ };
+ A._FutureListener.prototype = {
+ matchesErrorTest$1(asyncError) {
+ if ((this.state & 15) !== 6)
+ return true;
+ return this.result._zone.runUnary$2$2(type$.bool_Function_Object._as(this.callback), asyncError.error, type$.bool, type$.Object);
+ },
+ handleError$1(asyncError) {
+ var exception, _this = this,
+ errorCallback = _this.errorCallback,
+ result = null,
+ t1 = type$.dynamic,
+ t2 = type$.Object,
+ t3 = asyncError.error,
+ t4 = _this.result._zone;
+ if (type$.dynamic_Function_Object_StackTrace._is(errorCallback))
+ result = t4.runBinary$3$3(errorCallback, t3, asyncError.stackTrace, t1, t2, type$.StackTrace);
+ else
+ result = t4.runUnary$2$2(type$.dynamic_Function_Object._as(errorCallback), t3, t1, t2);
+ try {
+ t1 = _this.$ti._eval$1("2/")._as(result);
+ return t1;
+ } catch (exception) {
+ if (type$.TypeError._is(A.unwrapException(exception))) {
+ if ((_this.state & 1) !== 0)
+ throw A.wrapException(A.ArgumentError$("The error handler of Future.then must return a value of the returned future's type", "onError"));
+ throw A.wrapException(A.ArgumentError$("The error handler of Future.catchError must return a value of the future's type", "onError"));
+ } else
+ throw exception;
+ }
+ }
+ };
+ A._Future.prototype = {
+ _setChained$1(source) {
+ this._async$_state = this._async$_state & 1 | 4;
+ this._resultOrListeners = source;
+ },
+ then$1$2$onError(f, onError, $R) {
+ var currentZone, result, t2,
+ t1 = this.$ti;
+ t1._bind$1($R)._eval$1("1/(2)")._as(f);
+ currentZone = $.Zone__current;
+ if (currentZone === B.C__RootZone) {
+ if (onError != null && !type$.dynamic_Function_Object_StackTrace._is(onError) && !type$.dynamic_Function_Object._is(onError))
+ throw A.wrapException(A.ArgumentError$value(onError, "onError", string$.Error_));
+ } else {
+ f = currentZone.registerUnaryCallback$2$1(f, $R._eval$1("0/"), t1._precomputed1);
+ if (onError != null)
+ onError = A._registerErrorHandler(onError, currentZone);
+ }
+ result = new A._Future($.Zone__current, $R._eval$1("_Future<0>"));
+ t2 = onError == null ? 1 : 3;
+ this._addListener$1(new A._FutureListener(result, t2, f, onError, t1._eval$1("@<1>")._bind$1($R)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ then$1$1(f, $R) {
+ return this.then$1$2$onError(f, null, $R);
+ },
+ whenComplete$1(action) {
+ var t1, t2, result;
+ type$.dynamic_Function._as(action);
+ t1 = this.$ti;
+ t2 = $.Zone__current;
+ result = new A._Future(t2, t1);
+ if (t2 !== B.C__RootZone)
+ action = t2.registerCallback$1$1(action, type$.dynamic);
+ this._addListener$1(new A._FutureListener(result, 8, action, null, t1._eval$1("@<1>")._bind$1(t1._precomputed1)._eval$1("_FutureListener<1,2>")));
+ return result;
+ },
+ _setErrorObject$1(error) {
+ this._async$_state = this._async$_state & 1 | 16;
+ this._resultOrListeners = error;
+ },
+ _cloneResult$1(source) {
+ this._async$_state = source._async$_state & 30 | this._async$_state & 1;
+ this._resultOrListeners = source._resultOrListeners;
+ },
+ _addListener$1(listener) {
+ var source, _this = this,
+ t1 = _this._async$_state;
+ if (t1 <= 3) {
+ listener._nextListener = type$.nullable__FutureListener_dynamic_dynamic._as(_this._resultOrListeners);
+ _this._resultOrListeners = listener;
+ } else {
+ if ((t1 & 4) !== 0) {
+ source = type$._Future_dynamic._as(_this._resultOrListeners);
+ if ((source._async$_state & 24) === 0) {
+ source._addListener$1(listener);
+ return;
+ }
+ _this._cloneResult$1(source);
+ }
+ _this._zone.scheduleMicrotask$1(new A._Future__addListener_closure(_this, listener));
+ }
+ },
+ _prependListeners$1(listeners) {
+ var t1, existingListeners, next, cursor, next0, source, _this = this, _box_0 = {};
+ _box_0.listeners = listeners;
+ if (listeners == null)
+ return;
+ t1 = _this._async$_state;
+ if (t1 <= 3) {
+ existingListeners = type$.nullable__FutureListener_dynamic_dynamic._as(_this._resultOrListeners);
+ _this._resultOrListeners = listeners;
+ if (existingListeners != null) {
+ next = listeners._nextListener;
+ for (cursor = listeners; next != null; cursor = next, next = next0)
+ next0 = next._nextListener;
+ cursor._nextListener = existingListeners;
+ }
+ } else {
+ if ((t1 & 4) !== 0) {
+ source = type$._Future_dynamic._as(_this._resultOrListeners);
+ if ((source._async$_state & 24) === 0) {
+ source._prependListeners$1(listeners);
+ return;
+ }
+ _this._cloneResult$1(source);
+ }
+ _box_0.listeners = _this._reverseListeners$1(listeners);
+ _this._zone.scheduleMicrotask$1(new A._Future__prependListeners_closure(_box_0, _this));
+ }
+ },
+ _removeListeners$0() {
+ var current = type$.nullable__FutureListener_dynamic_dynamic._as(this._resultOrListeners);
+ this._resultOrListeners = null;
+ return this._reverseListeners$1(current);
+ },
+ _reverseListeners$1(listeners) {
+ var current, prev, next;
+ for (current = listeners, prev = null; current != null; prev = current, current = next) {
+ next = current._nextListener;
+ current._nextListener = prev;
+ }
+ return prev;
+ },
+ _chainForeignFuture$1(source) {
+ var e, s, exception, _this = this;
+ _this._async$_state ^= 2;
+ try {
+ source.then$1$2$onError(new A._Future__chainForeignFuture_closure(_this), new A._Future__chainForeignFuture_closure0(_this), type$.Null);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A.scheduleMicrotask(new A._Future__chainForeignFuture_closure1(_this, e, s));
+ }
+ },
+ _complete$1(value) {
+ var listeners, _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("1/")._as(value);
+ if (t1._eval$1("Future<1>")._is(value))
+ if (t1._is(value))
+ A._Future__chainCoreFutureSync(value, _this);
+ else
+ _this._chainForeignFuture$1(value);
+ else {
+ listeners = _this._removeListeners$0();
+ t1._precomputed1._as(value);
+ _this._async$_state = 8;
+ _this._resultOrListeners = value;
+ A._Future__propagateToListeners(_this, listeners);
+ }
+ },
+ _completeWithValue$1(value) {
+ var listeners, _this = this;
+ _this.$ti._precomputed1._as(value);
+ listeners = _this._removeListeners$0();
+ _this._async$_state = 8;
+ _this._resultOrListeners = value;
+ A._Future__propagateToListeners(_this, listeners);
+ },
+ _completeError$2(error, stackTrace) {
+ var listeners;
+ type$.Object._as(error);
+ type$.StackTrace._as(stackTrace);
+ listeners = this._removeListeners$0();
+ this._setErrorObject$1(A.AsyncError$(error, stackTrace));
+ A._Future__propagateToListeners(this, listeners);
+ },
+ _asyncComplete$1(value) {
+ var t1 = this.$ti;
+ t1._eval$1("1/")._as(value);
+ if (t1._eval$1("Future<1>")._is(value)) {
+ this._chainFuture$1(value);
+ return;
+ }
+ this._asyncCompleteWithValue$1(value);
+ },
+ _asyncCompleteWithValue$1(value) {
+ var _this = this;
+ _this.$ti._precomputed1._as(value);
+ _this._async$_state ^= 2;
+ _this._zone.scheduleMicrotask$1(new A._Future__asyncCompleteWithValue_closure(_this, value));
+ },
+ _chainFuture$1(value) {
+ var t1 = this.$ti;
+ t1._eval$1("Future<1>")._as(value);
+ if (t1._is(value)) {
+ A._Future__chainCoreFutureAsync(value, this);
+ return;
+ }
+ this._chainForeignFuture$1(value);
+ },
+ _asyncCompleteError$2(error, stackTrace) {
+ type$.StackTrace._as(stackTrace);
+ this._async$_state ^= 2;
+ this._zone.scheduleMicrotask$1(new A._Future__asyncCompleteError_closure(this, error, stackTrace));
+ },
+ $isFuture: 1
+ };
+ A._Future__addListener_closure.prototype = {
+ call$0() {
+ A._Future__propagateToListeners(this.$this, this.listener);
+ },
+ $signature: 0
+ };
+ A._Future__prependListeners_closure.prototype = {
+ call$0() {
+ A._Future__propagateToListeners(this.$this, this._box_0.listeners);
+ },
+ $signature: 0
+ };
+ A._Future__chainForeignFuture_closure.prototype = {
+ call$1(value) {
+ var error, stackTrace, exception,
+ t1 = this.$this;
+ t1._async$_state ^= 2;
+ try {
+ t1._completeWithValue$1(t1.$ti._precomputed1._as(value));
+ } catch (exception) {
+ error = A.unwrapException(exception);
+ stackTrace = A.getTraceFromException(exception);
+ t1._completeError$2(error, stackTrace);
+ }
+ },
+ $signature: 10
+ };
+ A._Future__chainForeignFuture_closure0.prototype = {
+ call$2(error, stackTrace) {
+ this.$this._completeError$2(type$.Object._as(error), type$.StackTrace._as(stackTrace));
+ },
+ $signature: 26
+ };
+ A._Future__chainForeignFuture_closure1.prototype = {
+ call$0() {
+ this.$this._completeError$2(this.e, this.s);
+ },
+ $signature: 0
+ };
+ A._Future__chainCoreFutureAsync_closure.prototype = {
+ call$0() {
+ A._Future__chainCoreFutureSync(this._box_0.source, this.target);
+ },
+ $signature: 0
+ };
+ A._Future__asyncCompleteWithValue_closure.prototype = {
+ call$0() {
+ this.$this._completeWithValue$1(this.value);
+ },
+ $signature: 0
+ };
+ A._Future__asyncCompleteError_closure.prototype = {
+ call$0() {
+ this.$this._completeError$2(this.error, this.stackTrace);
+ },
+ $signature: 0
+ };
+ A._Future__propagateToListeners_handleWhenCompleteCallback.prototype = {
+ call$0() {
+ var e, s, t1, exception, t2, originalSource, _this = this, completeResult = null;
+ try {
+ t1 = _this._box_0.listener;
+ completeResult = t1.result._zone.run$1$1(type$.dynamic_Function._as(t1.callback), type$.dynamic);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t1 = _this.hasError && type$.AsyncError._as(_this._box_1.source._resultOrListeners).error === e;
+ t2 = _this._box_0;
+ if (t1)
+ t2.listenerValueOrError = type$.AsyncError._as(_this._box_1.source._resultOrListeners);
+ else
+ t2.listenerValueOrError = A.AsyncError$(e, s);
+ t2.listenerHasError = true;
+ return;
+ }
+ if (completeResult instanceof A._Future && (completeResult._async$_state & 24) !== 0) {
+ if ((completeResult._async$_state & 16) !== 0) {
+ t1 = _this._box_0;
+ t1.listenerValueOrError = type$.AsyncError._as(completeResult._resultOrListeners);
+ t1.listenerHasError = true;
+ }
+ return;
+ }
+ if (completeResult instanceof A._Future) {
+ originalSource = _this._box_1.source;
+ t1 = _this._box_0;
+ t1.listenerValueOrError = completeResult.then$1$1(new A._Future__propagateToListeners_handleWhenCompleteCallback_closure(originalSource), type$.dynamic);
+ t1.listenerHasError = false;
+ }
+ },
+ $signature: 0
+ };
+ A._Future__propagateToListeners_handleWhenCompleteCallback_closure.prototype = {
+ call$1(_) {
+ return this.originalSource;
+ },
+ $signature: 28
+ };
+ A._Future__propagateToListeners_handleValueCallback.prototype = {
+ call$0() {
+ var e, s, t1, t2, t3, t4, t5, exception;
+ try {
+ t1 = this._box_0;
+ t2 = t1.listener;
+ t3 = t2.$ti;
+ t4 = t3._precomputed1;
+ t5 = t4._as(this.sourceResult);
+ t1.listenerValueOrError = t2.result._zone.runUnary$2$2(t3._eval$1("2/(1)")._as(t2.callback), t5, t3._eval$1("2/"), t4);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t1 = this._box_0;
+ t1.listenerValueOrError = A.AsyncError$(e, s);
+ t1.listenerHasError = true;
+ }
+ },
+ $signature: 0
+ };
+ A._Future__propagateToListeners_handleError.prototype = {
+ call$0() {
+ var asyncError, e, s, t1, exception, t2, _this = this;
+ try {
+ asyncError = type$.AsyncError._as(_this._box_1.source._resultOrListeners);
+ t1 = _this._box_0;
+ if (t1.listener.matchesErrorTest$1(asyncError) && t1.listener.errorCallback != null) {
+ t1.listenerValueOrError = t1.listener.handleError$1(asyncError);
+ t1.listenerHasError = false;
+ }
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t1 = type$.AsyncError._as(_this._box_1.source._resultOrListeners);
+ t2 = _this._box_0;
+ if (t1.error === e)
+ t2.listenerValueOrError = t1;
+ else
+ t2.listenerValueOrError = A.AsyncError$(e, s);
+ t2.listenerHasError = true;
+ }
+ },
+ $signature: 0
+ };
+ A._AsyncCallbackEntry.prototype = {};
+ A.Stream.prototype = {
+ pipe$1(streamConsumer) {
+ A._instanceType(this)._eval$1("StreamConsumer<Stream.T>")._as(streamConsumer);
+ return streamConsumer.addStream$1(0, this).then$1$1(new A.Stream_pipe_closure(streamConsumer), type$.dynamic);
+ },
+ get$length(_) {
+ var t1 = {},
+ future = new A._Future($.Zone__current, type$._Future_int);
+ t1.count = 0;
+ this.listen$4$cancelOnError$onDone$onError(new A.Stream_length_closure(t1, this), true, new A.Stream_length_closure0(t1, future), future.get$_completeError());
+ return future;
+ }
+ };
+ A.Stream_pipe_closure.prototype = {
+ call$1(_) {
+ return this.streamConsumer.close$0(0);
+ },
+ $signature: 29
+ };
+ A.Stream_length_closure.prototype = {
+ call$1(_) {
+ A._instanceType(this.$this)._eval$1("Stream.T")._as(_);
+ ++this._box_0.count;
+ },
+ $signature() {
+ return A._instanceType(this.$this)._eval$1("~(Stream.T)");
+ }
+ };
+ A.Stream_length_closure0.prototype = {
+ call$0() {
+ this.future._complete$1(this._box_0.count);
+ },
+ $signature: 0
+ };
+ A._StreamController.prototype = {
+ get$_pendingEvents() {
+ var t1, _this = this;
+ if ((_this._async$_state & 8) === 0)
+ return A._instanceType(_this)._eval$1("_PendingEvents<1>?")._as(_this._varData);
+ t1 = A._instanceType(_this);
+ return t1._eval$1("_PendingEvents<1>?")._as(t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData).varData);
+ },
+ _ensurePendingEvents$0() {
+ var events, t1, state, _this = this;
+ if ((_this._async$_state & 8) === 0) {
+ events = _this._varData;
+ if (events == null)
+ events = _this._varData = new A._PendingEvents(A._instanceType(_this)._eval$1("_PendingEvents<1>"));
+ return A._instanceType(_this)._eval$1("_PendingEvents<1>")._as(events);
+ }
+ t1 = A._instanceType(_this);
+ state = t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData);
+ events = state.varData;
+ if (events == null)
+ events = state.varData = new A._PendingEvents(t1._eval$1("_PendingEvents<1>"));
+ return t1._eval$1("_PendingEvents<1>")._as(events);
+ },
+ get$_async$_subscription() {
+ var varData = this._varData;
+ if ((this._async$_state & 8) !== 0)
+ varData = type$._StreamControllerAddStreamState_nullable_Object._as(varData).varData;
+ return A._instanceType(this)._eval$1("_ControllerSubscription<1>")._as(varData);
+ },
+ _badEventState$0() {
+ if ((this._async$_state & 4) !== 0)
+ return new A.StateError("Cannot add event after closing");
+ return new A.StateError("Cannot add event while adding a stream");
+ },
+ _ensureDoneFuture$0() {
+ var t1 = this._doneFuture;
+ if (t1 == null)
+ t1 = this._doneFuture = (this._async$_state & 2) !== 0 ? $.$get$Future__nullFuture() : new A._Future($.Zone__current, type$._Future_void);
+ return t1;
+ },
+ add$1(_, value) {
+ var _this = this;
+ A._instanceType(_this)._precomputed1._as(value);
+ if (_this._async$_state >= 4)
+ throw A.wrapException(_this._badEventState$0());
+ _this._add$1(0, value);
+ },
+ addError$2(error, stackTrace) {
+ var replacement,
+ t1 = type$.Object;
+ t1._as(error);
+ type$.nullable_StackTrace._as(stackTrace);
+ A.checkNotNullable(error, "error", t1);
+ if (this._async$_state >= 4)
+ throw A.wrapException(this._badEventState$0());
+ replacement = $.Zone__current.errorCallback$2(error, stackTrace);
+ if (replacement != null) {
+ error = replacement.error;
+ stackTrace = replacement.stackTrace;
+ } else if (stackTrace == null)
+ stackTrace = A.AsyncError_defaultStackTrace(error);
+ this._async$_addError$2(error, stackTrace);
+ },
+ addError$1(error) {
+ return this.addError$2(error, null);
+ },
+ close$0(_) {
+ var _this = this,
+ t1 = _this._async$_state;
+ if ((t1 & 4) !== 0)
+ return _this._ensureDoneFuture$0();
+ if (t1 >= 4)
+ throw A.wrapException(_this._badEventState$0());
+ t1 = _this._async$_state = t1 | 4;
+ if ((t1 & 1) !== 0)
+ _this._sendDone$0();
+ else if ((t1 & 3) === 0)
+ _this._ensurePendingEvents$0().add$1(0, B.C__DelayedDone);
+ return _this._ensureDoneFuture$0();
+ },
+ _add$1(_, value) {
+ var t2, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(value);
+ t2 = _this._async$_state;
+ if ((t2 & 1) !== 0)
+ _this._sendData$1(value);
+ else if ((t2 & 3) === 0)
+ _this._ensurePendingEvents$0().add$1(0, new A._DelayedData(value, t1._eval$1("_DelayedData<1>")));
+ },
+ _async$_addError$2(error, stackTrace) {
+ var t1 = this._async$_state;
+ if ((t1 & 1) !== 0)
+ this._sendError$2(error, stackTrace);
+ else if ((t1 & 3) === 0)
+ this._ensurePendingEvents$0().add$1(0, new A._DelayedError(error, stackTrace));
+ },
+ _subscribe$4(onData, onError, onDone, cancelOnError) {
+ var t2, t3, t4, t5, t6, subscription, pendingEvents, addState, _this = this,
+ t1 = A._instanceType(_this);
+ t1._eval$1("~(1)?")._as(onData);
+ type$.nullable_void_Function._as(onDone);
+ if ((_this._async$_state & 3) !== 0)
+ throw A.wrapException(A.StateError$("Stream has already been listened to."));
+ t2 = $.Zone__current;
+ t3 = cancelOnError ? 1 : 0;
+ t4 = A._BufferingStreamSubscription__registerDataHandler(t2, onData, t1._precomputed1);
+ t5 = A._BufferingStreamSubscription__registerErrorHandler(t2, onError);
+ t6 = onDone == null ? A.async___nullDoneHandler$closure() : onDone;
+ subscription = new A._ControllerSubscription(_this, t4, t5, t2.registerCallback$1$1(t6, type$.void), t2, t3, t1._eval$1("_ControllerSubscription<1>"));
+ pendingEvents = _this.get$_pendingEvents();
+ t3 = _this._async$_state |= 1;
+ if ((t3 & 8) !== 0) {
+ addState = t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData);
+ addState.varData = subscription;
+ addState.addSubscription.resume$0(0);
+ } else
+ _this._varData = subscription;
+ subscription._setPendingEvents$1(pendingEvents);
+ subscription._guardCallback$1(new A._StreamController__subscribe_closure(_this));
+ return subscription;
+ },
+ _recordCancel$1(subscription) {
+ var result, onCancel, cancelResult, e, s, exception, result0, _this = this,
+ t1 = A._instanceType(_this);
+ t1._eval$1("StreamSubscription<1>")._as(subscription);
+ result = null;
+ if ((_this._async$_state & 8) !== 0)
+ result = t1._eval$1("_StreamControllerAddStreamState<1>")._as(_this._varData).cancel$0(0);
+ _this._varData = null;
+ _this._async$_state = _this._async$_state & 4294967286 | 2;
+ onCancel = _this.onCancel;
+ if (onCancel != null)
+ if (result == null)
+ try {
+ cancelResult = onCancel.call$0();
+ if (cancelResult instanceof A._Future)
+ result = cancelResult;
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ result0 = new A._Future($.Zone__current, type$._Future_void);
+ result0._asyncCompleteError$2(e, s);
+ result = result0;
+ }
+ else
+ result = result.whenComplete$1(onCancel);
+ t1 = new A._StreamController__recordCancel_complete(_this);
+ if (result != null)
+ result = result.whenComplete$1(t1);
+ else
+ t1.call$0();
+ return result;
+ },
+ $isStreamConsumer: 1,
+ $isStreamSink: 1,
+ $isStreamController: 1,
+ $is_StreamControllerLifecycle: 1,
+ $is_EventDispatch: 1
+ };
+ A._StreamController__subscribe_closure.prototype = {
+ call$0() {
+ A._runGuarded(this.$this.onListen);
+ },
+ $signature: 0
+ };
+ A._StreamController__recordCancel_complete.prototype = {
+ call$0() {
+ var doneFuture = this.$this._doneFuture;
+ if (doneFuture != null && (doneFuture._async$_state & 30) === 0)
+ doneFuture._asyncComplete$1(null);
+ },
+ $signature: 0
+ };
+ A._SyncStreamControllerDispatch.prototype = {
+ _sendData$1(data) {
+ this.$ti._precomputed1._as(data);
+ this.get$_async$_subscription()._add$1(0, data);
+ },
+ _sendError$2(error, stackTrace) {
+ this.get$_async$_subscription()._async$_addError$2(error, stackTrace);
+ },
+ _sendDone$0() {
+ this.get$_async$_subscription()._close$0();
+ }
+ };
+ A._SyncStreamController.prototype = {};
+ A._ControllerStream.prototype = {
+ get$hashCode(_) {
+ return (A.Primitives_objectHashCode(this._controller) ^ 892482866) >>> 0;
+ },
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ if (this === other)
+ return true;
+ return other instanceof A._ControllerStream && other._controller === this._controller;
+ }
+ };
+ A._ControllerSubscription.prototype = {
+ _onCancel$0() {
+ return this._controller._recordCancel$1(this);
+ },
+ _onPause$0() {
+ var t1 = this._controller,
+ t2 = A._instanceType(t1);
+ t2._eval$1("StreamSubscription<1>")._as(this);
+ if ((t1._async$_state & 8) !== 0)
+ t2._eval$1("_StreamControllerAddStreamState<1>")._as(t1._varData).addSubscription.pause$0(0);
+ A._runGuarded(t1.onPause);
+ },
+ _onResume$0() {
+ var t1 = this._controller,
+ t2 = A._instanceType(t1);
+ t2._eval$1("StreamSubscription<1>")._as(this);
+ if ((t1._async$_state & 8) !== 0)
+ t2._eval$1("_StreamControllerAddStreamState<1>")._as(t1._varData).addSubscription.resume$0(0);
+ A._runGuarded(t1.onResume);
+ }
+ };
+ A._StreamSinkWrapper.prototype = {
+ add$1(_, data) {
+ this._target.add$1(0, this.$ti._precomputed1._as(data));
+ },
+ $isStreamConsumer: 1,
+ $isStreamSink: 1
+ };
+ A._AddStreamState_cancel_closure.prototype = {
+ call$0() {
+ this.$this.addStreamFuture._asyncComplete$1(null);
+ },
+ $signature: 3
+ };
+ A._BufferingStreamSubscription.prototype = {
+ _setPendingEvents$1(pendingEvents) {
+ var _this = this;
+ A._instanceType(_this)._eval$1("_PendingEvents<_BufferingStreamSubscription.T>?")._as(pendingEvents);
+ if (pendingEvents == null)
+ return;
+ _this.set$_pending(pendingEvents);
+ if (pendingEvents.lastPendingEvent != null) {
+ _this._async$_state = (_this._async$_state | 64) >>> 0;
+ pendingEvents.schedule$1(_this);
+ }
+ },
+ onData$1(handleData) {
+ var t1 = A._instanceType(this);
+ this.set$_onData(A._BufferingStreamSubscription__registerDataHandler(this._zone, t1._eval$1("~(_BufferingStreamSubscription.T)?")._as(handleData), t1._eval$1("_BufferingStreamSubscription.T")));
+ },
+ onError$1(_, handleError) {
+ this._onError = A._BufferingStreamSubscription__registerErrorHandler(this._zone, handleError);
+ },
+ cancel$0(_) {
+ var _this = this,
+ t1 = (_this._async$_state & 4294967279) >>> 0;
+ _this._async$_state = t1;
+ if ((t1 & 8) === 0)
+ _this._cancel$0();
+ t1 = _this._cancelFuture;
+ return t1 == null ? $.$get$Future__nullFuture() : t1;
+ },
+ _cancel$0() {
+ var t2, _this = this,
+ t1 = _this._async$_state = (_this._async$_state | 8) >>> 0;
+ if ((t1 & 64) !== 0) {
+ t2 = _this._pending;
+ if (t2._async$_state === 1)
+ t2._async$_state = 3;
+ }
+ if ((t1 & 32) === 0)
+ _this.set$_pending(null);
+ _this._cancelFuture = _this._onCancel$0();
+ },
+ _add$1(_, data) {
+ var t2, _this = this,
+ t1 = A._instanceType(_this);
+ t1._eval$1("_BufferingStreamSubscription.T")._as(data);
+ t2 = _this._async$_state;
+ if ((t2 & 8) !== 0)
+ return;
+ if (t2 < 32)
+ _this._sendData$1(data);
+ else
+ _this._addPending$1(new A._DelayedData(data, t1._eval$1("_DelayedData<_BufferingStreamSubscription.T>")));
+ },
+ _async$_addError$2(error, stackTrace) {
+ var t1 = this._async$_state;
+ if ((t1 & 8) !== 0)
+ return;
+ if (t1 < 32)
+ this._sendError$2(error, stackTrace);
+ else
+ this._addPending$1(new A._DelayedError(error, stackTrace));
+ },
+ _close$0() {
+ var _this = this,
+ t1 = _this._async$_state;
+ if ((t1 & 8) !== 0)
+ return;
+ t1 = (t1 | 2) >>> 0;
+ _this._async$_state = t1;
+ if (t1 < 32)
+ _this._sendDone$0();
+ else
+ _this._addPending$1(B.C__DelayedDone);
+ },
+ _onPause$0() {
+ },
+ _onResume$0() {
+ },
+ _onCancel$0() {
+ return null;
+ },
+ _addPending$1($event) {
+ var t1, _this = this,
+ pending = _this._pending;
+ if (pending == null) {
+ pending = new A._PendingEvents(A._instanceType(_this)._eval$1("_PendingEvents<_BufferingStreamSubscription.T>"));
+ _this.set$_pending(pending);
+ }
+ pending.add$1(0, $event);
+ t1 = _this._async$_state;
+ if ((t1 & 64) === 0) {
+ t1 = (t1 | 64) >>> 0;
+ _this._async$_state = t1;
+ if (t1 < 128)
+ pending.schedule$1(_this);
+ }
+ },
+ _sendData$1(data) {
+ var t2, _this = this,
+ t1 = A._instanceType(_this)._eval$1("_BufferingStreamSubscription.T");
+ t1._as(data);
+ t2 = _this._async$_state;
+ _this._async$_state = (t2 | 32) >>> 0;
+ _this._zone.runUnaryGuarded$1$2(_this._onData, data, t1);
+ _this._async$_state = (_this._async$_state & 4294967263) >>> 0;
+ _this._checkState$1((t2 & 4) !== 0);
+ },
+ _sendError$2(error, stackTrace) {
+ var cancelFuture, _this = this,
+ t1 = _this._async$_state,
+ t2 = new A._BufferingStreamSubscription__sendError_sendError(_this, error, stackTrace);
+ if ((t1 & 1) !== 0) {
+ _this._async$_state = (t1 | 16) >>> 0;
+ _this._cancel$0();
+ cancelFuture = _this._cancelFuture;
+ if (cancelFuture != null && cancelFuture !== $.$get$Future__nullFuture())
+ cancelFuture.whenComplete$1(t2);
+ else
+ t2.call$0();
+ } else {
+ t2.call$0();
+ _this._checkState$1((t1 & 4) !== 0);
+ }
+ },
+ _sendDone$0() {
+ var cancelFuture, _this = this,
+ t1 = new A._BufferingStreamSubscription__sendDone_sendDone(_this);
+ _this._cancel$0();
+ _this._async$_state = (_this._async$_state | 16) >>> 0;
+ cancelFuture = _this._cancelFuture;
+ if (cancelFuture != null && cancelFuture !== $.$get$Future__nullFuture())
+ cancelFuture.whenComplete$1(t1);
+ else
+ t1.call$0();
+ },
+ _guardCallback$1(callback) {
+ var t1, _this = this;
+ type$.void_Function._as(callback);
+ t1 = _this._async$_state;
+ _this._async$_state = (t1 | 32) >>> 0;
+ callback.call$0();
+ _this._async$_state = (_this._async$_state & 4294967263) >>> 0;
+ _this._checkState$1((t1 & 4) !== 0);
+ },
+ _checkState$1(wasInputPaused) {
+ var t2, isInputPaused, _this = this,
+ t1 = _this._async$_state;
+ if ((t1 & 64) !== 0 && _this._pending.lastPendingEvent == null) {
+ t1 = _this._async$_state = (t1 & 4294967231) >>> 0;
+ if ((t1 & 4) !== 0)
+ if (t1 < 128) {
+ t2 = _this._pending;
+ t2 = t2 == null ? null : t2.lastPendingEvent == null;
+ t2 = t2 !== false;
+ } else
+ t2 = false;
+ else
+ t2 = false;
+ if (t2) {
+ t1 = (t1 & 4294967291) >>> 0;
+ _this._async$_state = t1;
+ }
+ }
+ for (; true; wasInputPaused = isInputPaused) {
+ if ((t1 & 8) !== 0) {
+ _this.set$_pending(null);
+ return;
+ }
+ isInputPaused = (t1 & 4) !== 0;
+ if (wasInputPaused === isInputPaused)
+ break;
+ _this._async$_state = (t1 ^ 32) >>> 0;
+ if (isInputPaused)
+ _this._onPause$0();
+ else
+ _this._onResume$0();
+ t1 = (_this._async$_state & 4294967263) >>> 0;
+ _this._async$_state = t1;
+ }
+ if ((t1 & 64) !== 0 && t1 < 128)
+ _this._pending.schedule$1(_this);
+ },
+ set$_onData(_onData) {
+ this._onData = A._instanceType(this)._eval$1("~(_BufferingStreamSubscription.T)")._as(_onData);
+ },
+ set$_pending(_pending) {
+ this._pending = A._instanceType(this)._eval$1("_PendingEvents<_BufferingStreamSubscription.T>?")._as(_pending);
+ },
+ $isStreamSubscription: 1,
+ $is_EventDispatch: 1
+ };
+ A._BufferingStreamSubscription__sendError_sendError.prototype = {
+ call$0() {
+ var onError, t3, t4,
+ t1 = this.$this,
+ t2 = t1._async$_state;
+ if ((t2 & 8) !== 0 && (t2 & 16) === 0)
+ return;
+ t1._async$_state = (t2 | 32) >>> 0;
+ onError = t1._onError;
+ t2 = this.error;
+ t3 = type$.Object;
+ t4 = t1._zone;
+ if (type$.void_Function_Object_StackTrace._is(onError))
+ t4.runBinaryGuarded$2$3(onError, t2, this.stackTrace, t3, type$.StackTrace);
+ else
+ t4.runUnaryGuarded$1$2(type$.void_Function_Object._as(onError), t2, t3);
+ t1._async$_state = (t1._async$_state & 4294967263) >>> 0;
+ },
+ $signature: 0
+ };
+ A._BufferingStreamSubscription__sendDone_sendDone.prototype = {
+ call$0() {
+ var t1 = this.$this,
+ t2 = t1._async$_state;
+ if ((t2 & 16) === 0)
+ return;
+ t1._async$_state = (t2 | 42) >>> 0;
+ t1._zone.runGuarded$1(t1._onDone);
+ t1._async$_state = (t1._async$_state & 4294967263) >>> 0;
+ },
+ $signature: 0
+ };
+ A._StreamImpl.prototype = {
+ listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, onError) {
+ var t1 = this.$ti;
+ t1._eval$1("~(1)?")._as(onData);
+ type$.nullable_void_Function._as(onDone);
+ return this._controller._subscribe$4(t1._eval$1("~(1)?")._as(onData), onError, onDone, cancelOnError === true);
+ },
+ listen$1(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ },
+ listen$3$onDone$onError(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$2$onDone(onData, onDone) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, null);
+ },
+ listen$3$cancelOnError$onDone(onData, cancelOnError, onDone) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, null);
+ }
+ };
+ A._DelayedEvent.prototype = {
+ set$next(_, next) {
+ this.next = type$.nullable__DelayedEvent_dynamic._as(next);
+ },
+ get$next(receiver) {
+ return this.next;
+ }
+ };
+ A._DelayedData.prototype = {
+ perform$1(dispatch) {
+ this.$ti._eval$1("_EventDispatch<1>")._as(dispatch)._sendData$1(this.value);
+ }
+ };
+ A._DelayedError.prototype = {
+ perform$1(dispatch) {
+ dispatch._sendError$2(this.error, this.stackTrace);
+ }
+ };
+ A._DelayedDone.prototype = {
+ perform$1(dispatch) {
+ dispatch._sendDone$0();
+ },
+ get$next(_) {
+ return null;
+ },
+ set$next(_, _0) {
+ throw A.wrapException(A.StateError$("No events after a done."));
+ },
+ $is_DelayedEvent: 1
+ };
+ A._PendingEvents.prototype = {
+ schedule$1(dispatch) {
+ var t1, _this = this;
+ _this.$ti._eval$1("_EventDispatch<1>")._as(dispatch);
+ t1 = _this._async$_state;
+ if (t1 === 1)
+ return;
+ if (t1 >= 1) {
+ _this._async$_state = 1;
+ return;
+ }
+ A.scheduleMicrotask(new A._PendingEvents_schedule_closure(_this, dispatch));
+ _this._async$_state = 1;
+ },
+ add$1(_, $event) {
+ var _this = this,
+ lastEvent = _this.lastPendingEvent;
+ if (lastEvent == null)
+ _this.firstPendingEvent = _this.lastPendingEvent = $event;
+ else {
+ lastEvent.set$next(0, $event);
+ _this.lastPendingEvent = $event;
+ }
+ }
+ };
+ A._PendingEvents_schedule_closure.prototype = {
+ call$0() {
+ var t2, $event, nextEvent,
+ t1 = this.$this,
+ oldState = t1._async$_state;
+ t1._async$_state = 0;
+ if (oldState === 3)
+ return;
+ t2 = t1.$ti._eval$1("_EventDispatch<1>")._as(this.dispatch);
+ $event = t1.firstPendingEvent;
+ nextEvent = $event.get$next($event);
+ t1.firstPendingEvent = nextEvent;
+ if (nextEvent == null)
+ t1.lastPendingEvent = null;
+ $event.perform$1(t2);
+ },
+ $signature: 0
+ };
+ A._DoneStreamSubscription.prototype = {
+ onData$1(handleData) {
+ this.$ti._eval$1("~(1)?")._as(handleData);
+ },
+ onError$1(_, handleError) {
+ },
+ cancel$0(_) {
+ this._async$_state = -1;
+ this.set$_onDone(null);
+ return $.$get$Future__nullFuture();
+ },
+ _onMicrotask$0() {
+ var _0_0, doneHandler, t1, _this = this,
+ unscheduledState = _this._async$_state - 1;
+ if (unscheduledState === 0) {
+ _this._async$_state = -1;
+ _0_0 = _this._onDone;
+ if (_0_0 != null) {
+ doneHandler = _0_0;
+ t1 = true;
+ } else {
+ doneHandler = null;
+ t1 = false;
+ }
+ if (t1) {
+ _this.set$_onDone(null);
+ _this._zone.runGuarded$1(doneHandler);
+ }
+ } else
+ _this._async$_state = unscheduledState;
+ },
+ set$_onDone(_onDone) {
+ this._onDone = type$.nullable_void_Function._as(_onDone);
+ },
+ $isStreamSubscription: 1
+ };
+ A._EmptyStream.prototype = {
+ listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, onError) {
+ var t2,
+ t1 = this.$ti;
+ t1._eval$1("~(1)?")._as(onData);
+ type$.nullable_void_Function._as(onDone);
+ t2 = $.Zone__current;
+ t1 = new A._DoneStreamSubscription(t2, t1._eval$1("_DoneStreamSubscription<1>"));
+ A.scheduleMicrotask(t1.get$_onMicrotask());
+ if (onDone != null)
+ t1.set$_onDone(t2.registerCallback$1$1(onDone, type$.void));
+ return t1;
+ },
+ listen$1(onData) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, null, null);
+ },
+ listen$3$onDone$onError(onData, onDone, onError) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, null, onDone, onError);
+ },
+ listen$3$cancelOnError$onDone(onData, cancelOnError, onDone) {
+ return this.listen$4$cancelOnError$onDone$onError(onData, cancelOnError, onDone, null);
+ }
+ };
+ A._ZoneFunction.prototype = {};
+ A._ZoneSpecification.prototype = {$isZoneSpecification: 1};
+ A._ZoneDelegate.prototype = {$isZoneDelegate: 1};
+ A._Zone.prototype = {
+ _processUncaughtError$3(zone, error, stackTrace) {
+ var implZone, handler, parentDelegate, parentZone, currentZone, e, s, implementation, t1, exception;
+ type$.StackTrace._as(stackTrace);
+ implementation = this.get$_handleUncaughtError();
+ implZone = implementation.zone;
+ if (implZone === B.C__RootZone) {
+ A._rootHandleError(error, stackTrace);
+ return;
+ }
+ handler = implementation.$function;
+ parentDelegate = implZone.get$_parentDelegate();
+ t1 = J.get$parent$z(implZone);
+ t1.toString;
+ parentZone = t1;
+ currentZone = $.Zone__current;
+ try {
+ $.Zone__current = parentZone;
+ handler.call$5(implZone, parentDelegate, zone, error, stackTrace);
+ $.Zone__current = currentZone;
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ $.Zone__current = currentZone;
+ t1 = error === e ? stackTrace : s;
+ parentZone._processUncaughtError$3(implZone, e, t1);
+ }
+ },
+ $isZone: 1
+ };
+ A._CustomZone.prototype = {
+ get$_delegate() {
+ var t1 = this._delegateCache;
+ return t1 == null ? this._delegateCache = new A._ZoneDelegate(this) : t1;
+ },
+ get$_parentDelegate() {
+ return this.parent.get$_delegate();
+ },
+ get$errorZone() {
+ return this._handleUncaughtError.zone;
+ },
+ runGuarded$1(f) {
+ var e, s, exception;
+ type$.void_Function._as(f);
+ try {
+ this.run$1$1(f, type$.void);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ this._processUncaughtError$3(this, type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ runUnaryGuarded$1$2(f, arg, $T) {
+ var e, s, exception;
+ $T._eval$1("~(0)")._as(f);
+ $T._as(arg);
+ try {
+ this.runUnary$2$2(f, arg, type$.void, $T);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ this._processUncaughtError$3(this, type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ runBinaryGuarded$2$3(f, arg1, arg2, T1, T2) {
+ var e, s, exception;
+ T1._eval$1("@<0>")._bind$1(T2)._eval$1("~(1,2)")._as(f);
+ T1._as(arg1);
+ T2._as(arg2);
+ try {
+ this.runBinary$3$3(f, arg1, arg2, type$.void, T1, T2);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ this._processUncaughtError$3(this, type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ bindCallback$1$1(f, $R) {
+ return new A._CustomZone_bindCallback_closure(this, this.registerCallback$1$1($R._eval$1("0()")._as(f), $R), $R);
+ },
+ bindUnaryCallback$2$1(f, $R, $T) {
+ return new A._CustomZone_bindUnaryCallback_closure(this, this.registerUnaryCallback$2$1($R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f), $R, $T), $T, $R);
+ },
+ bindCallbackGuarded$1(f) {
+ return new A._CustomZone_bindCallbackGuarded_closure(this, this.registerCallback$1$1(type$.void_Function._as(f), type$.void));
+ },
+ bindUnaryCallbackGuarded$1$1(f, $T) {
+ return new A._CustomZone_bindUnaryCallbackGuarded_closure(this, this.registerUnaryCallback$2$1($T._eval$1("~(0)")._as(f), type$.void, $T), $T);
+ },
+ handleUncaughtError$2(error, stackTrace) {
+ this._processUncaughtError$3(this, error, type$.StackTrace._as(stackTrace));
+ },
+ fork$2$specification$zoneValues(specification, zoneValues) {
+ var implementation = this._fork,
+ t1 = implementation.zone;
+ return implementation.$function.call$5(t1, t1.get$_parentDelegate(), this, specification, zoneValues);
+ },
+ run$1$1(f, $R) {
+ var implementation, t1;
+ $R._eval$1("0()")._as(f);
+ implementation = this._run;
+ t1 = implementation.zone;
+ return implementation.$function.call$1$4(t1, t1.get$_parentDelegate(), this, f, $R);
+ },
+ runUnary$2$2(f, arg, $R, $T) {
+ var implementation, t1;
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f);
+ $T._as(arg);
+ implementation = this._runUnary;
+ t1 = implementation.zone;
+ return implementation.$function.call$2$5(t1, t1.get$_parentDelegate(), this, f, arg, $R, $T);
+ },
+ runBinary$3$3(f, arg1, arg2, $R, T1, T2) {
+ var implementation, t1;
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(f);
+ T1._as(arg1);
+ T2._as(arg2);
+ implementation = this._runBinary;
+ t1 = implementation.zone;
+ return implementation.$function.call$3$6(t1, t1.get$_parentDelegate(), this, f, arg1, arg2, $R, T1, T2);
+ },
+ registerCallback$1$1(callback, $R) {
+ var implementation, t1;
+ $R._eval$1("0()")._as(callback);
+ implementation = this._registerCallback;
+ t1 = implementation.zone;
+ return implementation.$function.call$1$4(t1, t1.get$_parentDelegate(), this, callback, $R);
+ },
+ registerUnaryCallback$2$1(callback, $R, $T) {
+ var implementation, t1;
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(callback);
+ implementation = this._registerUnaryCallback;
+ t1 = implementation.zone;
+ return implementation.$function.call$2$4(t1, t1.get$_parentDelegate(), this, callback, $R, $T);
+ },
+ registerBinaryCallback$3$1(callback, $R, T1, T2) {
+ var implementation, t1;
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(callback);
+ implementation = this._registerBinaryCallback;
+ t1 = implementation.zone;
+ return implementation.$function.call$3$4(t1, t1.get$_parentDelegate(), this, callback, $R, T1, T2);
+ },
+ errorCallback$2(error, stackTrace) {
+ var implementation, implementationZone;
+ A.checkNotNullable(error, "error", type$.Object);
+ implementation = this._errorCallback;
+ implementationZone = implementation.zone;
+ if (implementationZone === B.C__RootZone)
+ return null;
+ return implementation.$function.call$5(implementationZone, implementationZone.get$_parentDelegate(), this, error, stackTrace);
+ },
+ scheduleMicrotask$1(f) {
+ var implementation, t1;
+ type$.void_Function._as(f);
+ implementation = this._scheduleMicrotask;
+ t1 = implementation.zone;
+ return implementation.$function.call$4(t1, t1.get$_parentDelegate(), this, f);
+ },
+ createPeriodicTimer$2(duration, f) {
+ var implementation, t1;
+ type$.void_Function_Timer._as(f);
+ implementation = this._createPeriodicTimer;
+ t1 = implementation.zone;
+ return implementation.$function.call$5(t1, t1.get$_parentDelegate(), this, duration, f);
+ },
+ set$_handleUncaughtError(_handleUncaughtError) {
+ this._handleUncaughtError = type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace._as(_handleUncaughtError);
+ },
+ get$_run() {
+ return this._run;
+ },
+ get$_runUnary() {
+ return this._runUnary;
+ },
+ get$_runBinary() {
+ return this._runBinary;
+ },
+ get$_registerCallback() {
+ return this._registerCallback;
+ },
+ get$_registerUnaryCallback() {
+ return this._registerUnaryCallback;
+ },
+ get$_registerBinaryCallback() {
+ return this._registerBinaryCallback;
+ },
+ get$_errorCallback() {
+ return this._errorCallback;
+ },
+ get$_scheduleMicrotask() {
+ return this._scheduleMicrotask;
+ },
+ get$_createTimer() {
+ return this._createTimer;
+ },
+ get$_createPeriodicTimer() {
+ return this._createPeriodicTimer;
+ },
+ get$_print() {
+ return this._print;
+ },
+ get$_fork() {
+ return this._fork;
+ },
+ get$_handleUncaughtError() {
+ return this._handleUncaughtError;
+ },
+ get$parent(receiver) {
+ return this.parent;
+ },
+ get$_async$_map() {
+ return this._async$_map;
+ }
+ };
+ A._CustomZone_bindCallback_closure.prototype = {
+ call$0() {
+ return this.$this.run$1$1(this.registered, this.R);
+ },
+ $signature() {
+ return this.R._eval$1("0()");
+ }
+ };
+ A._CustomZone_bindUnaryCallback_closure.prototype = {
+ call$1(arg) {
+ var _this = this,
+ t1 = _this.T;
+ return _this.$this.runUnary$2$2(_this.registered, t1._as(arg), _this.R, t1);
+ },
+ $signature() {
+ return this.R._eval$1("@<0>")._bind$1(this.T)._eval$1("1(2)");
+ }
+ };
+ A._CustomZone_bindCallbackGuarded_closure.prototype = {
+ call$0() {
+ return this.$this.runGuarded$1(this.registered);
+ },
+ $signature: 0
+ };
+ A._CustomZone_bindUnaryCallbackGuarded_closure.prototype = {
+ call$1(arg) {
+ var t1 = this.T;
+ return this.$this.runUnaryGuarded$1$2(this.registered, t1._as(arg), t1);
+ },
+ $signature() {
+ return this.T._eval$1("~(0)");
+ }
+ };
+ A._rootHandleError_closure.prototype = {
+ call$0() {
+ A.Error_throwWithStackTrace(this.error, this.stackTrace);
+ },
+ $signature: 0
+ };
+ A._RootZone.prototype = {
+ get$_run() {
+ return B._ZoneFunction__RootZone__rootRun;
+ },
+ get$_runUnary() {
+ return B._ZoneFunction__RootZone__rootRunUnary;
+ },
+ get$_runBinary() {
+ return B._ZoneFunction__RootZone__rootRunBinary;
+ },
+ get$_registerCallback() {
+ return B._ZoneFunction__RootZone__rootRegisterCallback;
+ },
+ get$_registerUnaryCallback() {
+ return B._ZoneFunction_Eeh;
+ },
+ get$_registerBinaryCallback() {
+ return B._ZoneFunction_7G2;
+ },
+ get$_errorCallback() {
+ return B._ZoneFunction__RootZone__rootErrorCallback;
+ },
+ get$_scheduleMicrotask() {
+ return B._ZoneFunction__RootZone__rootScheduleMicrotask;
+ },
+ get$_createTimer() {
+ return B._ZoneFunction__RootZone__rootCreateTimer;
+ },
+ get$_createPeriodicTimer() {
+ return B._ZoneFunction_3bB;
+ },
+ get$_print() {
+ return B._ZoneFunction__RootZone__rootPrint;
+ },
+ get$_fork() {
+ return B._ZoneFunction__RootZone__rootFork;
+ },
+ get$_handleUncaughtError() {
+ return B._ZoneFunction_NMc;
+ },
+ get$parent(_) {
+ return null;
+ },
+ get$_async$_map() {
+ return $.$get$_RootZone__rootMap();
+ },
+ get$_delegate() {
+ var t1 = $._RootZone__rootDelegate;
+ return t1 == null ? $._RootZone__rootDelegate = new A._ZoneDelegate(this) : t1;
+ },
+ get$_parentDelegate() {
+ var t1 = $._RootZone__rootDelegate;
+ return t1 == null ? $._RootZone__rootDelegate = new A._ZoneDelegate(this) : t1;
+ },
+ get$errorZone() {
+ return this;
+ },
+ runGuarded$1(f) {
+ var e, s, exception;
+ type$.void_Function._as(f);
+ try {
+ if (B.C__RootZone === $.Zone__current) {
+ f.call$0();
+ return;
+ }
+ A._rootRun(null, null, this, f, type$.void);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A._rootHandleError(type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ runUnaryGuarded$1$2(f, arg, $T) {
+ var e, s, exception;
+ $T._eval$1("~(0)")._as(f);
+ $T._as(arg);
+ try {
+ if (B.C__RootZone === $.Zone__current) {
+ f.call$1(arg);
+ return;
+ }
+ A._rootRunUnary(null, null, this, f, arg, type$.void, $T);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A._rootHandleError(type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ runBinaryGuarded$2$3(f, arg1, arg2, T1, T2) {
+ var e, s, exception;
+ T1._eval$1("@<0>")._bind$1(T2)._eval$1("~(1,2)")._as(f);
+ T1._as(arg1);
+ T2._as(arg2);
+ try {
+ if (B.C__RootZone === $.Zone__current) {
+ f.call$2(arg1, arg2);
+ return;
+ }
+ A._rootRunBinary(null, null, this, f, arg1, arg2, type$.void, T1, T2);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ A._rootHandleError(type$.Object._as(e), type$.StackTrace._as(s));
+ }
+ },
+ bindCallback$1$1(f, $R) {
+ return new A._RootZone_bindCallback_closure(this, $R._eval$1("0()")._as(f), $R);
+ },
+ bindUnaryCallback$2$1(f, $R, $T) {
+ return new A._RootZone_bindUnaryCallback_closure(this, $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f), $T, $R);
+ },
+ bindCallbackGuarded$1(f) {
+ return new A._RootZone_bindCallbackGuarded_closure(this, type$.void_Function._as(f));
+ },
+ bindUnaryCallbackGuarded$1$1(f, $T) {
+ return new A._RootZone_bindUnaryCallbackGuarded_closure(this, $T._eval$1("~(0)")._as(f), $T);
+ },
+ handleUncaughtError$2(error, stackTrace) {
+ A._rootHandleError(error, type$.StackTrace._as(stackTrace));
+ },
+ fork$2$specification$zoneValues(specification, zoneValues) {
+ return A._rootFork(null, null, this, specification, zoneValues);
+ },
+ run$1$1(f, $R) {
+ $R._eval$1("0()")._as(f);
+ if ($.Zone__current === B.C__RootZone)
+ return f.call$0();
+ return A._rootRun(null, null, this, f, $R);
+ },
+ runUnary$2$2(f, arg, $R, $T) {
+ $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f);
+ $T._as(arg);
+ if ($.Zone__current === B.C__RootZone)
+ return f.call$1(arg);
+ return A._rootRunUnary(null, null, this, f, arg, $R, $T);
+ },
+ runBinary$3$3(f, arg1, arg2, $R, T1, T2) {
+ $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(f);
+ T1._as(arg1);
+ T2._as(arg2);
+ if ($.Zone__current === B.C__RootZone)
+ return f.call$2(arg1, arg2);
+ return A._rootRunBinary(null, null, this, f, arg1, arg2, $R, T1, T2);
+ },
+ registerCallback$1$1(f, $R) {
+ return $R._eval$1("0()")._as(f);
+ },
+ registerUnaryCallback$2$1(f, $R, $T) {
+ return $R._eval$1("@<0>")._bind$1($T)._eval$1("1(2)")._as(f);
+ },
+ registerBinaryCallback$3$1(f, $R, T1, T2) {
+ return $R._eval$1("@<0>")._bind$1(T1)._bind$1(T2)._eval$1("1(2,3)")._as(f);
+ },
+ errorCallback$2(error, stackTrace) {
+ return null;
+ },
+ scheduleMicrotask$1(f) {
+ A._rootScheduleMicrotask(null, null, this, type$.void_Function._as(f));
+ },
+ createPeriodicTimer$2(duration, f) {
+ return A.Timer__createPeriodicTimer(duration, type$.void_Function_Timer._as(f));
+ }
+ };
+ A._RootZone_bindCallback_closure.prototype = {
+ call$0() {
+ return this.$this.run$1$1(this.f, this.R);
+ },
+ $signature() {
+ return this.R._eval$1("0()");
+ }
+ };
+ A._RootZone_bindUnaryCallback_closure.prototype = {
+ call$1(arg) {
+ var _this = this,
+ t1 = _this.T;
+ return _this.$this.runUnary$2$2(_this.f, t1._as(arg), _this.R, t1);
+ },
+ $signature() {
+ return this.R._eval$1("@<0>")._bind$1(this.T)._eval$1("1(2)");
+ }
+ };
+ A._RootZone_bindCallbackGuarded_closure.prototype = {
+ call$0() {
+ return this.$this.runGuarded$1(this.f);
+ },
+ $signature: 0
+ };
+ A._RootZone_bindUnaryCallbackGuarded_closure.prototype = {
+ call$1(arg) {
+ var t1 = this.T;
+ return this.$this.runUnaryGuarded$1$2(this.f, t1._as(arg), t1);
+ },
+ $signature() {
+ return this.T._eval$1("~(0)");
+ }
+ };
+ A.runZonedGuarded_closure.prototype = {
+ call$5($self, $parent, zone, error, stackTrace) {
+ var e, s, exception, t2,
+ t1 = type$.StackTrace;
+ t1._as(stackTrace);
+ try {
+ this.parentZone.runBinary$3$3(this.onError, error, stackTrace, type$.void, type$.Object, t1);
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ s = A.getTraceFromException(exception);
+ t2 = $parent._delegationTarget;
+ if (e === error)
+ t2._processUncaughtError$3(zone, error, stackTrace);
+ else
+ t2._processUncaughtError$3(zone, type$.Object._as(e), t1._as(s));
+ }
+ },
+ $signature: 34
+ };
+ A._HashMap.prototype = {
+ get$length(_) {
+ return this._collection$_length;
+ },
+ get$isEmpty(_) {
+ return this._collection$_length === 0;
+ },
+ get$keys(_) {
+ return new A._HashMapKeyIterable(this, A._instanceType(this)._eval$1("_HashMapKeyIterable<1>"));
+ },
+ containsKey$1(_, key) {
+ var strings, nums;
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = this._collection$_strings;
+ return strings == null ? false : strings[key] != null;
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = this._collection$_nums;
+ return nums == null ? false : nums[key] != null;
+ } else
+ return this._containsKey$1(key);
+ },
+ _containsKey$1(key) {
+ var rest = this._collection$_rest;
+ if (rest == null)
+ return false;
+ return this._findBucketIndex$2(this._getBucket$2(rest, key), key) >= 0;
+ },
+ $index(_, key) {
+ var strings, t1, nums;
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = this._collection$_strings;
+ t1 = strings == null ? null : A._HashMap__getTableEntry(strings, key);
+ return t1;
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = this._collection$_nums;
+ t1 = nums == null ? null : A._HashMap__getTableEntry(nums, key);
+ return t1;
+ } else
+ return this._get$1(0, key);
+ },
+ _get$1(_, key) {
+ var bucket, index,
+ rest = this._collection$_rest;
+ if (rest == null)
+ return null;
+ bucket = this._getBucket$2(rest, key);
+ index = this._findBucketIndex$2(bucket, key);
+ return index < 0 ? null : bucket[index + 1];
+ },
+ $indexSet(_, key, value) {
+ var strings, nums, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ if (typeof key == "string" && key !== "__proto__") {
+ strings = _this._collection$_strings;
+ _this._collection$_addHashTableEntry$3(strings == null ? _this._collection$_strings = A._HashMap__newHashTable() : strings, key, value);
+ } else if (typeof key == "number" && (key & 1073741823) === key) {
+ nums = _this._collection$_nums;
+ _this._collection$_addHashTableEntry$3(nums == null ? _this._collection$_nums = A._HashMap__newHashTable() : nums, key, value);
+ } else
+ _this._set$2(key, value);
+ },
+ _set$2(key, value) {
+ var rest, hash, bucket, index, _this = this,
+ t1 = A._instanceType(_this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ rest = _this._collection$_rest;
+ if (rest == null)
+ rest = _this._collection$_rest = A._HashMap__newHashTable();
+ hash = _this._computeHashCode$1(key);
+ bucket = rest[hash];
+ if (bucket == null) {
+ A._HashMap__setTableEntry(rest, hash, [key, value]);
+ ++_this._collection$_length;
+ _this._keys = null;
+ } else {
+ index = _this._findBucketIndex$2(bucket, key);
+ if (index >= 0)
+ bucket[index + 1] = value;
+ else {
+ bucket.push(key, value);
+ ++_this._collection$_length;
+ _this._keys = null;
+ }
+ }
+ },
+ forEach$1(_, action) {
+ var keys, $length, t2, i, key, t3, _this = this,
+ t1 = A._instanceType(_this);
+ t1._eval$1("~(1,2)")._as(action);
+ keys = _this._computeKeys$0();
+ for ($length = keys.length, t2 = t1._precomputed1, t1 = t1._rest[1], i = 0; i < $length; ++i) {
+ key = keys[i];
+ t2._as(key);
+ t3 = _this.$index(0, key);
+ action.call$2(key, t3 == null ? t1._as(t3) : t3);
+ if (keys !== _this._keys)
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ },
+ _computeKeys$0() {
+ var strings, names, entries, index, i, nums, rest, bucket, $length, i0, _this = this,
+ result = _this._keys;
+ if (result != null)
+ return result;
+ result = A.List_List$filled(_this._collection$_length, null, false, type$.dynamic);
+ strings = _this._collection$_strings;
+ if (strings != null) {
+ names = Object.getOwnPropertyNames(strings);
+ entries = names.length;
+ for (index = 0, i = 0; i < entries; ++i) {
+ result[index] = names[i];
+ ++index;
+ }
+ } else
+ index = 0;
+ nums = _this._collection$_nums;
+ if (nums != null) {
+ names = Object.getOwnPropertyNames(nums);
+ entries = names.length;
+ for (i = 0; i < entries; ++i) {
+ result[index] = +names[i];
+ ++index;
+ }
+ }
+ rest = _this._collection$_rest;
+ if (rest != null) {
+ names = Object.getOwnPropertyNames(rest);
+ entries = names.length;
+ for (i = 0; i < entries; ++i) {
+ bucket = rest[names[i]];
+ $length = bucket.length;
+ for (i0 = 0; i0 < $length; i0 += 2) {
+ result[index] = bucket[i0];
+ ++index;
+ }
+ }
+ }
+ return _this._keys = result;
+ },
+ _collection$_addHashTableEntry$3(table, key, value) {
+ var t1 = A._instanceType(this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ if (table[key] == null) {
+ ++this._collection$_length;
+ this._keys = null;
+ }
+ A._HashMap__setTableEntry(table, key, value);
+ },
+ _computeHashCode$1(key) {
+ return J.get$hashCode$(key) & 1073741823;
+ },
+ _getBucket$2(table, key) {
+ return table[this._computeHashCode$1(key)];
+ },
+ _findBucketIndex$2(bucket, key) {
+ var $length, i;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; i += 2)
+ if (J.$eq$(bucket[i], key))
+ return i;
+ return -1;
+ }
+ };
+ A._IdentityHashMap.prototype = {
+ _computeHashCode$1(key) {
+ return A.objectHashCode(key) & 1073741823;
+ },
+ _findBucketIndex$2(bucket, key) {
+ var $length, i, t1;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; i += 2) {
+ t1 = bucket[i];
+ if (t1 == null ? key == null : t1 === key)
+ return i;
+ }
+ return -1;
+ }
+ };
+ A._HashMapKeyIterable.prototype = {
+ get$length(_) {
+ return this._collection$_map._collection$_length;
+ },
+ get$isEmpty(_) {
+ return this._collection$_map._collection$_length === 0;
+ },
+ get$isNotEmpty(_) {
+ return this._collection$_map._collection$_length !== 0;
+ },
+ get$iterator(_) {
+ var t1 = this._collection$_map;
+ return new A._HashMapKeyIterator(t1, t1._computeKeys$0(), this.$ti._eval$1("_HashMapKeyIterator<1>"));
+ },
+ contains$1(_, element) {
+ return this._collection$_map.containsKey$1(0, element);
+ }
+ };
+ A._HashMapKeyIterator.prototype = {
+ get$current(_) {
+ var t1 = this._collection$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var _this = this,
+ keys = _this._keys,
+ offset = _this._offset,
+ t1 = _this._collection$_map;
+ if (keys !== t1._keys)
+ throw A.wrapException(A.ConcurrentModificationError$(t1));
+ else if (offset >= keys.length) {
+ _this.set$_collection$_current(null);
+ return false;
+ } else {
+ _this.set$_collection$_current(keys[offset]);
+ _this._offset = offset + 1;
+ return true;
+ }
+ },
+ set$_collection$_current(_current) {
+ this._collection$_current = this.$ti._eval$1("1?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A._LinkedHashSet.prototype = {
+ get$iterator(_) {
+ var _this = this,
+ t1 = new A._LinkedHashSetIterator(_this, _this._collection$_modifications, _this.$ti._eval$1("_LinkedHashSetIterator<1>"));
+ t1._collection$_cell = _this._collection$_first;
+ return t1;
+ },
+ get$length(_) {
+ return this._collection$_length;
+ },
+ get$isEmpty(_) {
+ return this._collection$_length === 0;
+ },
+ get$isNotEmpty(_) {
+ return this._collection$_length !== 0;
+ },
+ contains$1(_, object) {
+ var nums;
+ if ((object & 1073741823) === object) {
+ nums = this._collection$_nums;
+ if (nums == null)
+ return false;
+ return type$.nullable__LinkedHashSetCell._as(nums[object]) != null;
+ } else
+ return this._contains$1(object);
+ },
+ _contains$1(object) {
+ var rest = this._collection$_rest;
+ if (rest == null)
+ return false;
+ return this._findBucketIndex$2(rest[B.JSInt_methods.get$hashCode(object) & 1073741823], object) >= 0;
+ },
+ add$1(_, element) {
+ var strings, nums, _this = this;
+ _this.$ti._precomputed1._as(element);
+ if (typeof element == "string" && element !== "__proto__") {
+ strings = _this._collection$_strings;
+ return _this._collection$_addHashTableEntry$2(strings == null ? _this._collection$_strings = A._LinkedHashSet__newHashTable() : strings, element);
+ } else if (typeof element == "number" && (element & 1073741823) === element) {
+ nums = _this._collection$_nums;
+ return _this._collection$_addHashTableEntry$2(nums == null ? _this._collection$_nums = A._LinkedHashSet__newHashTable() : nums, element);
+ } else
+ return _this._collection$_add$1(0, element);
+ },
+ _collection$_add$1(_, element) {
+ var rest, hash, bucket, _this = this;
+ _this.$ti._precomputed1._as(element);
+ rest = _this._collection$_rest;
+ if (rest == null)
+ rest = _this._collection$_rest = A._LinkedHashSet__newHashTable();
+ hash = J.get$hashCode$(element) & 1073741823;
+ bucket = rest[hash];
+ if (bucket == null)
+ rest[hash] = [_this._collection$_newLinkedCell$1(element)];
+ else {
+ if (_this._findBucketIndex$2(bucket, element) >= 0)
+ return false;
+ bucket.push(_this._collection$_newLinkedCell$1(element));
+ }
+ return true;
+ },
+ remove$1(_, object) {
+ if ((object & 1073741823) === object)
+ return this._collection$_removeHashTableEntry$2(this._collection$_nums, object);
+ else
+ return this._remove$1(0, object);
+ },
+ _remove$1(_, object) {
+ var hash, bucket, index, cell,
+ rest = this._collection$_rest;
+ if (rest == null)
+ return false;
+ hash = B.JSInt_methods.get$hashCode(object) & 1073741823;
+ bucket = rest[hash];
+ index = this._findBucketIndex$2(bucket, object);
+ if (index < 0)
+ return false;
+ cell = bucket.splice(index, 1)[0];
+ if (0 === bucket.length)
+ delete rest[hash];
+ this._collection$_unlinkCell$1(cell);
+ return true;
+ },
+ _collection$_addHashTableEntry$2(table, element) {
+ this.$ti._precomputed1._as(element);
+ if (type$.nullable__LinkedHashSetCell._as(table[element]) != null)
+ return false;
+ table[element] = this._collection$_newLinkedCell$1(element);
+ return true;
+ },
+ _collection$_removeHashTableEntry$2(table, element) {
+ var cell;
+ if (table == null)
+ return false;
+ cell = type$.nullable__LinkedHashSetCell._as(table[element]);
+ if (cell == null)
+ return false;
+ this._collection$_unlinkCell$1(cell);
+ delete table[element];
+ return true;
+ },
+ _collection$_modified$0() {
+ this._collection$_modifications = this._collection$_modifications + 1 & 1073741823;
+ },
+ _collection$_newLinkedCell$1(element) {
+ var t1, _this = this,
+ cell = new A._LinkedHashSetCell(_this.$ti._precomputed1._as(element));
+ if (_this._collection$_first == null)
+ _this._collection$_first = _this._collection$_last = cell;
+ else {
+ t1 = _this._collection$_last;
+ t1.toString;
+ cell._collection$_previous = t1;
+ _this._collection$_last = t1._collection$_next = cell;
+ }
+ ++_this._collection$_length;
+ _this._collection$_modified$0();
+ return cell;
+ },
+ _collection$_unlinkCell$1(cell) {
+ var _this = this,
+ previous = cell._collection$_previous,
+ next = cell._collection$_next;
+ if (previous == null)
+ _this._collection$_first = next;
+ else
+ previous._collection$_next = next;
+ if (next == null)
+ _this._collection$_last = previous;
+ else
+ next._collection$_previous = previous;
+ --_this._collection$_length;
+ _this._collection$_modified$0();
+ },
+ _findBucketIndex$2(bucket, element) {
+ var $length, i;
+ if (bucket == null)
+ return -1;
+ $length = bucket.length;
+ for (i = 0; i < $length; ++i)
+ if (J.$eq$(bucket[i]._element, element))
+ return i;
+ return -1;
+ }
+ };
+ A._LinkedHashSetCell.prototype = {};
+ A._LinkedHashSetIterator.prototype = {
+ get$current(_) {
+ var t1 = this._collection$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ moveNext$0() {
+ var _this = this,
+ cell = _this._collection$_cell,
+ t1 = _this._set;
+ if (_this._collection$_modifications !== t1._collection$_modifications)
+ throw A.wrapException(A.ConcurrentModificationError$(t1));
+ else if (cell == null) {
+ _this.set$_collection$_current(null);
+ return false;
+ } else {
+ _this.set$_collection$_current(_this.$ti._eval$1("1?")._as(cell._element));
+ _this._collection$_cell = cell._collection$_next;
+ return true;
+ }
+ },
+ set$_collection$_current(_current) {
+ this._collection$_current = this.$ti._eval$1("1?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A.ListBase.prototype = {
+ get$iterator(receiver) {
+ return new A.ListIterator(receiver, this.get$length(receiver), A.instanceType(receiver)._eval$1("ListIterator<ListBase.E>"));
+ },
+ elementAt$1(receiver, index) {
+ return this.$index(receiver, index);
+ },
+ get$isEmpty(receiver) {
+ return this.get$length(receiver) === 0;
+ },
+ get$isNotEmpty(receiver) {
+ return !this.get$isEmpty(receiver);
+ },
+ get$first(receiver) {
+ if (this.get$length(receiver) === 0)
+ throw A.wrapException(A.IterableElementError_noElement());
+ return this.$index(receiver, 0);
+ },
+ map$1$1(receiver, f, $T) {
+ var t1 = A.instanceType(receiver);
+ return new A.MappedListIterable(receiver, t1._bind$1($T)._eval$1("1(ListBase.E)")._as(f), t1._eval$1("@<ListBase.E>")._bind$1($T)._eval$1("MappedListIterable<1,2>"));
+ },
+ skip$1(receiver, count) {
+ return A.SubListIterable$(receiver, count, null, A.instanceType(receiver)._eval$1("ListBase.E"));
+ },
+ cast$1$0(receiver, $R) {
+ return new A.CastList(receiver, A.instanceType(receiver)._eval$1("@<ListBase.E>")._bind$1($R)._eval$1("CastList<1,2>"));
+ },
+ fillRange$3(receiver, start, end, fill) {
+ var i;
+ A.instanceType(receiver)._eval$1("ListBase.E?")._as(fill);
+ A.RangeError_checkValidRange(start, end, this.get$length(receiver));
+ for (i = start; i < end; ++i)
+ this.$indexSet(receiver, i, fill);
+ },
+ toString$0(receiver) {
+ return A.Iterable_iterableToFullString(receiver, "[", "]");
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.MapBase.prototype = {
+ forEach$1(receiver, action) {
+ var t2, key, t3,
+ t1 = A.instanceType(receiver);
+ t1._eval$1("~(MapBase.K,MapBase.V)")._as(action);
+ for (t2 = J.get$iterator$ax(this.get$keys(receiver)), t1 = t1._eval$1("MapBase.V"); t2.moveNext$0();) {
+ key = t2.get$current(t2);
+ t3 = this.$index(receiver, key);
+ action.call$2(key, t3 == null ? t1._as(t3) : t3);
+ }
+ },
+ containsKey$1(receiver, key) {
+ return J.contains$1$asx(this.get$keys(receiver), key);
+ },
+ get$length(receiver) {
+ return J.get$length$asx(this.get$keys(receiver));
+ },
+ get$isEmpty(receiver) {
+ return J.get$isEmpty$asx(this.get$keys(receiver));
+ },
+ toString$0(receiver) {
+ return A.MapBase_mapToString(receiver);
+ },
+ $isMap: 1
+ };
+ A.MapBase_mapToString_closure.prototype = {
+ call$2(k, v) {
+ var t2,
+ t1 = this._box_0;
+ if (!t1.first)
+ this.result._contents += ", ";
+ t1.first = false;
+ t1 = this.result;
+ t2 = t1._contents += A.S(k);
+ t1._contents = t2 + ": ";
+ t1._contents += A.S(v);
+ },
+ $signature: 16
+ };
+ A._UnmodifiableMapMixin.prototype = {
+ $indexSet(_, key, value) {
+ var t1 = A._instanceType(this);
+ t1._precomputed1._as(key);
+ t1._rest[1]._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot modify unmodifiable map"));
+ }
+ };
+ A.MapView.prototype = {
+ $index(_, key) {
+ return J.$index$asx(this._collection$_map, key);
+ },
+ $indexSet(_, key, value) {
+ var t1 = A._instanceType(this);
+ J.$indexSet$ax(this._collection$_map, t1._precomputed1._as(key), t1._rest[1]._as(value));
+ },
+ containsKey$1(_, key) {
+ return J.containsKey$1$x(this._collection$_map, key);
+ },
+ forEach$1(_, action) {
+ J.forEach$1$x(this._collection$_map, A._instanceType(this)._eval$1("~(1,2)")._as(action));
+ },
+ get$isEmpty(_) {
+ return J.get$isEmpty$asx(this._collection$_map);
+ },
+ get$length(_) {
+ return J.get$length$asx(this._collection$_map);
+ },
+ get$keys(_) {
+ return J.get$keys$x(this._collection$_map);
+ },
+ toString$0(_) {
+ return J.toString$0$(this._collection$_map);
+ },
+ $isMap: 1
+ };
+ A.UnmodifiableMapView.prototype = {};
+ A.SetBase.prototype = {
+ get$isEmpty(_) {
+ return this._collection$_length === 0;
+ },
+ get$isNotEmpty(_) {
+ return this._collection$_length !== 0;
+ },
+ map$1$1(_, f, $T) {
+ var t1 = this.$ti;
+ return new A.EfficientLengthMappedIterable(this, t1._bind$1($T)._eval$1("1(2)")._as(f), t1._eval$1("@<1>")._bind$1($T)._eval$1("EfficientLengthMappedIterable<1,2>"));
+ },
+ toString$0(_) {
+ return A.Iterable_iterableToFullString(this, "{", "}");
+ },
+ skip$1(_, n) {
+ return A.SkipIterable_SkipIterable(this, n, this.$ti._precomputed1);
+ },
+ elementAt$1(_, index) {
+ var iterator, skipCount, t1, _this = this;
+ A.RangeError_checkNotNegative(index, "index");
+ iterator = A._LinkedHashSetIterator$(_this, _this._collection$_modifications, _this.$ti._precomputed1);
+ for (skipCount = index; iterator.moveNext$0();) {
+ if (skipCount === 0) {
+ t1 = iterator._collection$_current;
+ return t1 == null ? iterator.$ti._precomputed1._as(t1) : t1;
+ }
+ --skipCount;
+ }
+ throw A.wrapException(A.IndexError$withLength(index, index - skipCount, _this, "index"));
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isSet: 1
+ };
+ A._SetBase.prototype = {};
+ A._UnmodifiableMapView_MapView__UnmodifiableMapMixin.prototype = {};
+ A._JsonMap.prototype = {
+ $index(_, key) {
+ var result,
+ t1 = this._processed;
+ if (t1 == null)
+ return this._data.$index(0, key);
+ else if (typeof key != "string")
+ return null;
+ else {
+ result = t1[key];
+ return typeof result == "undefined" ? this._process$1(key) : result;
+ }
+ },
+ get$length(_) {
+ return this._processed == null ? this._data._length : this._convert$_computeKeys$0().length;
+ },
+ get$isEmpty(_) {
+ return this.get$length(this) === 0;
+ },
+ get$keys(_) {
+ var t1;
+ if (this._processed == null) {
+ t1 = this._data;
+ return new A.LinkedHashMapKeyIterable(t1, A._instanceType(t1)._eval$1("LinkedHashMapKeyIterable<1>"));
+ }
+ return new A._JsonMapKeyIterable(this);
+ },
+ $indexSet(_, key, value) {
+ var processed, original, _this = this;
+ if (_this._processed == null)
+ _this._data.$indexSet(0, key, value);
+ else if (_this.containsKey$1(0, key)) {
+ processed = _this._processed;
+ processed[key] = value;
+ original = _this._original;
+ if (original == null ? processed != null : original !== processed)
+ original[key] = null;
+ } else
+ _this._upgrade$0().$indexSet(0, key, value);
+ },
+ containsKey$1(_, key) {
+ if (this._processed == null)
+ return this._data.containsKey$1(0, key);
+ return Object.prototype.hasOwnProperty.call(this._original, key);
+ },
+ forEach$1(_, f) {
+ var keys, i, key, value, _this = this;
+ type$.void_Function_String_dynamic._as(f);
+ if (_this._processed == null)
+ return _this._data.forEach$1(0, f);
+ keys = _this._convert$_computeKeys$0();
+ for (i = 0; i < keys.length; ++i) {
+ key = keys[i];
+ value = _this._processed[key];
+ if (typeof value == "undefined") {
+ value = A._convertJsonToDartLazy(_this._original[key]);
+ _this._processed[key] = value;
+ }
+ f.call$2(key, value);
+ if (keys !== _this._data)
+ throw A.wrapException(A.ConcurrentModificationError$(_this));
+ }
+ },
+ _convert$_computeKeys$0() {
+ var keys = type$.nullable_List_dynamic._as(this._data);
+ if (keys == null)
+ keys = this._data = A._setArrayType(Object.keys(this._original), type$.JSArray_String);
+ return keys;
+ },
+ _upgrade$0() {
+ var result, keys, i, t1, key, _this = this;
+ if (_this._processed == null)
+ return _this._data;
+ result = A.LinkedHashMap_LinkedHashMap$_empty(type$.String, type$.dynamic);
+ keys = _this._convert$_computeKeys$0();
+ for (i = 0; t1 = keys.length, i < t1; ++i) {
+ key = keys[i];
+ result.$indexSet(0, key, _this.$index(0, key));
+ }
+ if (t1 === 0)
+ B.JSArray_methods.add$1(keys, "");
+ else
+ B.JSArray_methods.clear$0(keys);
+ _this._original = _this._processed = null;
+ return _this._data = result;
+ },
+ _process$1(key) {
+ var result;
+ if (!Object.prototype.hasOwnProperty.call(this._original, key))
+ return null;
+ result = A._convertJsonToDartLazy(this._original[key]);
+ return this._processed[key] = result;
+ }
+ };
+ A._JsonMapKeyIterable.prototype = {
+ get$length(_) {
+ var t1 = this._convert$_parent;
+ return t1.get$length(t1);
+ },
+ elementAt$1(_, index) {
+ var t1 = this._convert$_parent;
+ if (t1._processed == null)
+ t1 = t1.get$keys(t1).elementAt$1(0, index);
+ else {
+ t1 = t1._convert$_computeKeys$0();
+ if (!(index >= 0 && index < t1.length))
+ return A.ioore(t1, index);
+ t1 = t1[index];
+ }
+ return t1;
+ },
+ get$iterator(_) {
+ var t1 = this._convert$_parent;
+ if (t1._processed == null) {
+ t1 = t1.get$keys(t1);
+ t1 = t1.get$iterator(t1);
+ } else {
+ t1 = t1._convert$_computeKeys$0();
+ t1 = new J.ArrayIterator(t1, t1.length, A._arrayInstanceType(t1)._eval$1("ArrayIterator<1>"));
+ }
+ return t1;
+ },
+ contains$1(_, key) {
+ return this._convert$_parent.containsKey$1(0, key);
+ }
+ };
+ A.Utf8Decoder__decoder_closure.prototype = {
+ call$0() {
+ var t1, exception;
+ try {
+ t1 = new TextDecoder("utf-8", {fatal: true});
+ return t1;
+ } catch (exception) {
+ }
+ return null;
+ },
+ $signature: 2
+ };
+ A.Utf8Decoder__decoderNonfatal_closure.prototype = {
+ call$0() {
+ var t1, exception;
+ try {
+ t1 = new TextDecoder("utf-8", {fatal: false});
+ return t1;
+ } catch (exception) {
+ }
+ return null;
+ },
+ $signature: 2
+ };
+ A.AsciiCodec.prototype = {
+ encode$1(source) {
+ return B.AsciiEncoder_127.convert$1(source);
+ }
+ };
+ A._UnicodeSubsetEncoder.prototype = {
+ convert$1(string) {
+ var stringLength, $length, result, t1, i, codeUnit;
+ A._asString(string);
+ stringLength = string.length;
+ $length = A.RangeError_checkValidRange(0, null, stringLength) - 0;
+ result = new Uint8Array($length);
+ for (t1 = ~this._subsetMask, i = 0; i < $length; ++i) {
+ if (!(i < stringLength))
+ return A.ioore(string, i);
+ codeUnit = string.charCodeAt(i);
+ if ((codeUnit & t1) !== 0)
+ throw A.wrapException(A.ArgumentError$value(string, "string", "Contains invalid characters."));
+ if (!(i < $length))
+ return A.ioore(result, i);
+ result[i] = codeUnit;
+ }
+ return result;
+ }
+ };
+ A.AsciiEncoder.prototype = {};
+ A.Base64Codec.prototype = {
+ normalize$3(_, source, start, end) {
+ var inverseAlphabet, t2, i, sliceStart, buffer, firstPadding, firstPaddingSourceIndex, paddingCount, i0, char, i1, digit1, t3, digit2, char0, value, endLength, $length,
+ _s64_ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+ _s31_ = "Invalid base64 encoding length ",
+ t1 = source.length;
+ end = A.RangeError_checkValidRange(start, end, t1);
+ inverseAlphabet = $.$get$_Base64Decoder__inverseAlphabet();
+ for (t2 = inverseAlphabet.length, i = start, sliceStart = i, buffer = null, firstPadding = -1, firstPaddingSourceIndex = -1, paddingCount = 0; i < end; i = i0) {
+ i0 = i + 1;
+ if (!(i < t1))
+ return A.ioore(source, i);
+ char = source.charCodeAt(i);
+ if (char === 37) {
+ i1 = i0 + 2;
+ if (i1 <= end) {
+ if (!(i0 < t1))
+ return A.ioore(source, i0);
+ digit1 = A.hexDigitValue(source.charCodeAt(i0));
+ t3 = i0 + 1;
+ if (!(t3 < t1))
+ return A.ioore(source, t3);
+ digit2 = A.hexDigitValue(source.charCodeAt(t3));
+ char0 = digit1 * 16 + digit2 - (digit2 & 256);
+ if (char0 === 37)
+ char0 = -1;
+ i0 = i1;
+ } else
+ char0 = -1;
+ } else
+ char0 = char;
+ if (0 <= char0 && char0 <= 127) {
+ if (!(char0 >= 0 && char0 < t2))
+ return A.ioore(inverseAlphabet, char0);
+ value = inverseAlphabet[char0];
+ if (value >= 0) {
+ if (!(value < 64))
+ return A.ioore(_s64_, value);
+ char0 = _s64_.charCodeAt(value);
+ if (char0 === char)
+ continue;
+ char = char0;
+ } else {
+ if (value === -1) {
+ if (firstPadding < 0) {
+ t3 = buffer == null ? null : buffer._contents.length;
+ if (t3 == null)
+ t3 = 0;
+ firstPadding = t3 + (i - sliceStart);
+ firstPaddingSourceIndex = i;
+ }
+ ++paddingCount;
+ if (char === 61)
+ continue;
+ }
+ char = char0;
+ }
+ if (value !== -2) {
+ if (buffer == null) {
+ buffer = new A.StringBuffer("");
+ t3 = buffer;
+ } else
+ t3 = buffer;
+ t3._contents += B.JSString_methods.substring$2(source, sliceStart, i);
+ t3._contents += A.Primitives_stringFromCharCode(char);
+ sliceStart = i0;
+ continue;
+ }
+ }
+ throw A.wrapException(A.FormatException$("Invalid base64 data", source, i));
+ }
+ if (buffer != null) {
+ t1 = buffer._contents += B.JSString_methods.substring$2(source, sliceStart, end);
+ t2 = t1.length;
+ if (firstPadding >= 0)
+ A.Base64Codec__checkPadding(source, firstPaddingSourceIndex, end, firstPadding, paddingCount, t2);
+ else {
+ endLength = B.JSInt_methods.$mod(t2 - 1, 4) + 1;
+ if (endLength === 1)
+ throw A.wrapException(A.FormatException$(_s31_, source, end));
+ for (; endLength < 4;) {
+ t1 += "=";
+ buffer._contents = t1;
+ ++endLength;
+ }
+ }
+ t1 = buffer._contents;
+ return B.JSString_methods.replaceRange$3(source, start, end, t1.charCodeAt(0) == 0 ? t1 : t1);
+ }
+ $length = end - start;
+ if (firstPadding >= 0)
+ A.Base64Codec__checkPadding(source, firstPaddingSourceIndex, end, firstPadding, paddingCount, $length);
+ else {
+ endLength = B.JSInt_methods.$mod($length, 4);
+ if (endLength === 1)
+ throw A.wrapException(A.FormatException$(_s31_, source, end));
+ if (endLength > 1)
+ source = B.JSString_methods.replaceRange$3(source, end, end, endLength === 2 ? "==" : "=");
+ }
+ return source;
+ }
+ };
+ A.Base64Encoder.prototype = {};
+ A.Codec.prototype = {};
+ A._FusedCodec.prototype = {};
+ A.Converter.prototype = {$isStreamTransformer: 1};
+ A.Encoding.prototype = {};
+ A.JsonUnsupportedObjectError.prototype = {
+ toString$0(_) {
+ var safeString = A.Error_safeToString(this.unsupportedObject);
+ return (this.cause != null ? "Converting object to an encodable object failed:" : "Converting object did not return an encodable object:") + " " + safeString;
+ }
+ };
+ A.JsonCyclicError.prototype = {
+ toString$0(_) {
+ return "Cyclic error in JSON stringify";
+ }
+ };
+ A.JsonCodec.prototype = {
+ decode$2$reviver(_, source, reviver) {
+ var t1 = A._parseJson(source, this.get$decoder()._reviver);
+ return t1;
+ },
+ encode$2$toEncodable(value, toEncodable) {
+ var t1 = A._JsonStringStringifier_stringify(value, this.get$encoder()._toEncodable, null);
+ return t1;
+ },
+ get$encoder() {
+ return B.JsonEncoder_null;
+ },
+ get$decoder() {
+ return B.JsonDecoder_null;
+ }
+ };
+ A.JsonEncoder.prototype = {};
+ A.JsonDecoder.prototype = {};
+ A._JsonStringifier.prototype = {
+ writeStringContent$1(s) {
+ var offset, i, charCode, t1, t2, _this = this,
+ $length = s.length;
+ for (offset = 0, i = 0; i < $length; ++i) {
+ charCode = s.charCodeAt(i);
+ if (charCode > 92) {
+ if (charCode >= 55296) {
+ t1 = charCode & 64512;
+ if (t1 === 55296) {
+ t2 = i + 1;
+ t2 = !(t2 < $length && (s.charCodeAt(t2) & 64512) === 56320);
+ } else
+ t2 = false;
+ if (!t2)
+ if (t1 === 56320) {
+ t1 = i - 1;
+ t1 = !(t1 >= 0 && (s.charCodeAt(t1) & 64512) === 55296);
+ } else
+ t1 = false;
+ else
+ t1 = true;
+ if (t1) {
+ if (i > offset)
+ _this.writeStringSlice$3(s, offset, i);
+ offset = i + 1;
+ _this.writeCharCode$1(92);
+ _this.writeCharCode$1(117);
+ _this.writeCharCode$1(100);
+ t1 = charCode >>> 8 & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ t1 = charCode >>> 4 & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ t1 = charCode & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ }
+ }
+ continue;
+ }
+ if (charCode < 32) {
+ if (i > offset)
+ _this.writeStringSlice$3(s, offset, i);
+ offset = i + 1;
+ _this.writeCharCode$1(92);
+ switch (charCode) {
+ case 8:
+ _this.writeCharCode$1(98);
+ break;
+ case 9:
+ _this.writeCharCode$1(116);
+ break;
+ case 10:
+ _this.writeCharCode$1(110);
+ break;
+ case 12:
+ _this.writeCharCode$1(102);
+ break;
+ case 13:
+ _this.writeCharCode$1(114);
+ break;
+ default:
+ _this.writeCharCode$1(117);
+ _this.writeCharCode$1(48);
+ _this.writeCharCode$1(48);
+ t1 = charCode >>> 4 & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ t1 = charCode & 15;
+ _this.writeCharCode$1(t1 < 10 ? 48 + t1 : 87 + t1);
+ break;
+ }
+ } else if (charCode === 34 || charCode === 92) {
+ if (i > offset)
+ _this.writeStringSlice$3(s, offset, i);
+ offset = i + 1;
+ _this.writeCharCode$1(92);
+ _this.writeCharCode$1(charCode);
+ }
+ }
+ if (offset === 0)
+ _this.writeString$1(s);
+ else if (offset < $length)
+ _this.writeStringSlice$3(s, offset, $length);
+ },
+ _checkCycle$1(object) {
+ var t1, t2, i, t3;
+ for (t1 = this._seen, t2 = t1.length, i = 0; i < t2; ++i) {
+ t3 = t1[i];
+ if (object == null ? t3 == null : object === t3)
+ throw A.wrapException(new A.JsonCyclicError(object, null));
+ }
+ B.JSArray_methods.add$1(t1, object);
+ },
+ writeObject$1(object) {
+ var customJson, e, t1, exception, _this = this;
+ if (_this.writeJsonValue$1(object))
+ return;
+ _this._checkCycle$1(object);
+ try {
+ customJson = _this._toEncodable.call$1(object);
+ if (!_this.writeJsonValue$1(customJson)) {
+ t1 = A.JsonUnsupportedObjectError$(object, null, _this.get$_partialResult());
+ throw A.wrapException(t1);
+ }
+ t1 = _this._seen;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ } catch (exception) {
+ e = A.unwrapException(exception);
+ t1 = A.JsonUnsupportedObjectError$(object, e, _this.get$_partialResult());
+ throw A.wrapException(t1);
+ }
+ },
+ writeJsonValue$1(object) {
+ var t1, success, _this = this;
+ if (typeof object == "number") {
+ if (!isFinite(object))
+ return false;
+ _this.writeNumber$1(object);
+ return true;
+ } else if (object === true) {
+ _this.writeString$1("true");
+ return true;
+ } else if (object === false) {
+ _this.writeString$1("false");
+ return true;
+ } else if (object == null) {
+ _this.writeString$1("null");
+ return true;
+ } else if (typeof object == "string") {
+ _this.writeString$1('"');
+ _this.writeStringContent$1(object);
+ _this.writeString$1('"');
+ return true;
+ } else if (type$.List_dynamic._is(object)) {
+ _this._checkCycle$1(object);
+ _this.writeList$1(object);
+ t1 = _this._seen;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ return true;
+ } else if (type$.Map_dynamic_dynamic._is(object)) {
+ _this._checkCycle$1(object);
+ success = _this.writeMap$1(object);
+ t1 = _this._seen;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ return success;
+ } else
+ return false;
+ },
+ writeList$1(list) {
+ var t1, i, _this = this;
+ _this.writeString$1("[");
+ t1 = J.getInterceptor$asx(list);
+ if (t1.get$isNotEmpty(list)) {
+ _this.writeObject$1(t1.$index(list, 0));
+ for (i = 1; i < t1.get$length(list); ++i) {
+ _this.writeString$1(",");
+ _this.writeObject$1(t1.$index(list, i));
+ }
+ }
+ _this.writeString$1("]");
+ },
+ writeMap$1(map) {
+ var t2, keyValueList, i, separator, _this = this, _box_0 = {},
+ t1 = J.getInterceptor$asx(map);
+ if (t1.get$isEmpty(map)) {
+ _this.writeString$1("{}");
+ return true;
+ }
+ t2 = t1.get$length(map) * 2;
+ keyValueList = A.List_List$filled(t2, null, false, type$.nullable_Object);
+ i = _box_0.i = 0;
+ _box_0.allStringKeys = true;
+ t1.forEach$1(map, new A._JsonStringifier_writeMap_closure(_box_0, keyValueList));
+ if (!_box_0.allStringKeys)
+ return false;
+ _this.writeString$1("{");
+ for (separator = '"'; i < t2; i += 2, separator = ',"') {
+ _this.writeString$1(separator);
+ _this.writeStringContent$1(A._asString(keyValueList[i]));
+ _this.writeString$1('":');
+ t1 = i + 1;
+ if (!(t1 < t2))
+ return A.ioore(keyValueList, t1);
+ _this.writeObject$1(keyValueList[t1]);
+ }
+ _this.writeString$1("}");
+ return true;
+ }
+ };
+ A._JsonStringifier_writeMap_closure.prototype = {
+ call$2(key, value) {
+ var t1, t2;
+ if (typeof key != "string")
+ this._box_0.allStringKeys = false;
+ t1 = this.keyValueList;
+ t2 = this._box_0;
+ B.JSArray_methods.$indexSet(t1, t2.i++, key);
+ B.JSArray_methods.$indexSet(t1, t2.i++, value);
+ },
+ $signature: 16
+ };
+ A._JsonStringStringifier.prototype = {
+ get$_partialResult() {
+ var t1 = this._sink;
+ return t1 instanceof A.StringBuffer ? t1.toString$0(0) : null;
+ },
+ writeNumber$1(number) {
+ this._sink.write$1(0, B.JSNumber_methods.toString$0(number));
+ },
+ writeString$1(string) {
+ this._sink.write$1(0, string);
+ },
+ writeStringSlice$3(string, start, end) {
+ this._sink.write$1(0, B.JSString_methods.substring$2(string, start, end));
+ },
+ writeCharCode$1(charCode) {
+ this._sink.writeCharCode$1(charCode);
+ }
+ };
+ A.Utf8Codec.prototype = {};
+ A.Utf8Encoder.prototype = {
+ convert$1(string) {
+ var stringLength, end, $length, t1, encoder, t2;
+ A._asString(string);
+ stringLength = string.length;
+ end = A.RangeError_checkValidRange(0, null, stringLength);
+ $length = end - 0;
+ if ($length === 0)
+ return new Uint8Array(0);
+ t1 = new Uint8Array($length * 3);
+ encoder = new A._Utf8Encoder(t1);
+ if (encoder._fillBuffer$3(string, 0, end) !== end) {
+ t2 = end - 1;
+ if (!(t2 >= 0 && t2 < stringLength))
+ return A.ioore(string, t2);
+ encoder._writeReplacementCharacter$0();
+ }
+ return B.NativeUint8List_methods.sublist$2(t1, 0, encoder._bufferIndex);
+ }
+ };
+ A._Utf8Encoder.prototype = {
+ _writeReplacementCharacter$0() {
+ var _this = this,
+ t1 = _this._buffer,
+ t2 = _this._bufferIndex,
+ t3 = _this._bufferIndex = t2 + 1,
+ t4 = t1.length;
+ if (!(t2 < t4))
+ return A.ioore(t1, t2);
+ t1[t2] = 239;
+ t2 = _this._bufferIndex = t3 + 1;
+ if (!(t3 < t4))
+ return A.ioore(t1, t3);
+ t1[t3] = 191;
+ _this._bufferIndex = t2 + 1;
+ if (!(t2 < t4))
+ return A.ioore(t1, t2);
+ t1[t2] = 189;
+ },
+ _writeSurrogate$2(leadingSurrogate, nextCodeUnit) {
+ var rune, t1, t2, t3, t4, _this = this;
+ if ((nextCodeUnit & 64512) === 56320) {
+ rune = 65536 + ((leadingSurrogate & 1023) << 10) | nextCodeUnit & 1023;
+ t1 = _this._buffer;
+ t2 = _this._bufferIndex;
+ t3 = _this._bufferIndex = t2 + 1;
+ t4 = t1.length;
+ if (!(t2 < t4))
+ return A.ioore(t1, t2);
+ t1[t2] = rune >>> 18 | 240;
+ t2 = _this._bufferIndex = t3 + 1;
+ if (!(t3 < t4))
+ return A.ioore(t1, t3);
+ t1[t3] = rune >>> 12 & 63 | 128;
+ t3 = _this._bufferIndex = t2 + 1;
+ if (!(t2 < t4))
+ return A.ioore(t1, t2);
+ t1[t2] = rune >>> 6 & 63 | 128;
+ _this._bufferIndex = t3 + 1;
+ if (!(t3 < t4))
+ return A.ioore(t1, t3);
+ t1[t3] = rune & 63 | 128;
+ return true;
+ } else {
+ _this._writeReplacementCharacter$0();
+ return false;
+ }
+ },
+ _fillBuffer$3(str, start, end) {
+ var t1, t2, t3, stringIndex, codeUnit, t4, t5, _this = this;
+ if (start !== end) {
+ t1 = end - 1;
+ if (!(t1 >= 0 && t1 < str.length))
+ return A.ioore(str, t1);
+ t1 = (str.charCodeAt(t1) & 64512) === 55296;
+ } else
+ t1 = false;
+ if (t1)
+ --end;
+ for (t1 = _this._buffer, t2 = t1.length, t3 = str.length, stringIndex = start; stringIndex < end; ++stringIndex) {
+ if (!(stringIndex < t3))
+ return A.ioore(str, stringIndex);
+ codeUnit = str.charCodeAt(stringIndex);
+ if (codeUnit <= 127) {
+ t4 = _this._bufferIndex;
+ if (t4 >= t2)
+ break;
+ _this._bufferIndex = t4 + 1;
+ t1[t4] = codeUnit;
+ } else {
+ t4 = codeUnit & 64512;
+ if (t4 === 55296) {
+ if (_this._bufferIndex + 4 > t2)
+ break;
+ t4 = stringIndex + 1;
+ if (!(t4 < t3))
+ return A.ioore(str, t4);
+ if (_this._writeSurrogate$2(codeUnit, str.charCodeAt(t4)))
+ stringIndex = t4;
+ } else if (t4 === 56320) {
+ if (_this._bufferIndex + 3 > t2)
+ break;
+ _this._writeReplacementCharacter$0();
+ } else if (codeUnit <= 2047) {
+ t4 = _this._bufferIndex;
+ t5 = t4 + 1;
+ if (t5 >= t2)
+ break;
+ _this._bufferIndex = t5;
+ if (!(t4 < t2))
+ return A.ioore(t1, t4);
+ t1[t4] = codeUnit >>> 6 | 192;
+ _this._bufferIndex = t5 + 1;
+ t1[t5] = codeUnit & 63 | 128;
+ } else {
+ t4 = _this._bufferIndex;
+ if (t4 + 2 >= t2)
+ break;
+ t5 = _this._bufferIndex = t4 + 1;
+ if (!(t4 < t2))
+ return A.ioore(t1, t4);
+ t1[t4] = codeUnit >>> 12 | 224;
+ t4 = _this._bufferIndex = t5 + 1;
+ if (!(t5 < t2))
+ return A.ioore(t1, t5);
+ t1[t5] = codeUnit >>> 6 & 63 | 128;
+ _this._bufferIndex = t4 + 1;
+ if (!(t4 < t2))
+ return A.ioore(t1, t4);
+ t1[t4] = codeUnit & 63 | 128;
+ }
+ }
+ }
+ return stringIndex;
+ }
+ };
+ A.Utf8Decoder.prototype = {
+ convert$1(codeUnits) {
+ var t1, result;
+ type$.List_int._as(codeUnits);
+ t1 = this._allowMalformed;
+ result = A.Utf8Decoder__convertIntercepted(t1, codeUnits, 0, null);
+ if (result != null)
+ return result;
+ return new A._Utf8Decoder(t1).convertGeneral$4(codeUnits, 0, null, true);
+ }
+ };
+ A._Utf8Decoder.prototype = {
+ convertGeneral$4(codeUnits, start, maybeEnd, single) {
+ var end, bytes, errorOffset, result, t1, message, _this = this;
+ type$.List_int._as(codeUnits);
+ end = A.RangeError_checkValidRange(start, maybeEnd, J.get$length$asx(codeUnits));
+ if (start === end)
+ return "";
+ if (type$.Uint8List._is(codeUnits)) {
+ bytes = codeUnits;
+ errorOffset = 0;
+ } else {
+ bytes = A._Utf8Decoder__makeUint8List(codeUnits, start, end);
+ end -= start;
+ errorOffset = start;
+ start = 0;
+ }
+ result = _this._convertRecursive$4(bytes, start, end, single);
+ t1 = _this._state;
+ if ((t1 & 1) !== 0) {
+ message = A._Utf8Decoder_errorDescription(t1);
+ _this._state = 0;
+ throw A.wrapException(A.FormatException$(message, codeUnits, errorOffset + _this._charOrIndex));
+ }
+ return result;
+ },
+ _convertRecursive$4(bytes, start, end, single) {
+ var mid, s1, _this = this;
+ if (end - start > 1000) {
+ mid = B.JSInt_methods._tdivFast$1(start + end, 2);
+ s1 = _this._convertRecursive$4(bytes, start, mid, false);
+ if ((_this._state & 1) !== 0)
+ return s1;
+ return s1 + _this._convertRecursive$4(bytes, mid, end, single);
+ }
+ return _this.decodeGeneral$4(bytes, start, end, single);
+ },
+ decodeGeneral$4(bytes, start, end, single) {
+ var byte, t2, type, t3, i0, markEnd, i1, m, _this = this,
+ _s256_ = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFFFFFFFFFFFFFFFGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHIHHHJEEBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBKCCCCCCCCCCCCDCLONNNMEEEEEEEEEEE",
+ _s144_ = " \x000:XECCCCCN:lDb \x000:XECCCCCNvlDb \x000:XECCCCCN:lDb AAAAA\x00\x00\x00\x00\x00AAAAA00000AAAAA:::::AAAAAGG000AAAAA00KKKAAAAAG::::AAAAA:IIIIAAAAA000\x800AAAAA\x00\x00\x00\x00 AAAAA",
+ _65533 = 65533,
+ state = _this._state,
+ char = _this._charOrIndex,
+ buffer = new A.StringBuffer(""),
+ i = start + 1,
+ t1 = bytes.length;
+ if (!(start >= 0 && start < t1))
+ return A.ioore(bytes, start);
+ byte = bytes[start];
+ $label0$0:
+ for (t2 = _this.allowMalformed; true;) {
+ for (; true; i = i0) {
+ if (!(byte >= 0 && byte < 256))
+ return A.ioore(_s256_, byte);
+ type = _s256_.charCodeAt(byte) & 31;
+ char = state <= 32 ? byte & 61694 >>> type : (byte & 63 | char << 6) >>> 0;
+ t3 = state + type;
+ if (!(t3 >= 0 && t3 < 144))
+ return A.ioore(_s144_, t3);
+ state = _s144_.charCodeAt(t3);
+ if (state === 0) {
+ buffer._contents += A.Primitives_stringFromCharCode(char);
+ if (i === end)
+ break $label0$0;
+ break;
+ } else if ((state & 1) !== 0) {
+ if (t2)
+ switch (state) {
+ case 69:
+ case 67:
+ buffer._contents += A.Primitives_stringFromCharCode(_65533);
+ break;
+ case 65:
+ buffer._contents += A.Primitives_stringFromCharCode(_65533);
+ --i;
+ break;
+ default:
+ t3 = buffer._contents += A.Primitives_stringFromCharCode(_65533);
+ buffer._contents = t3 + A.Primitives_stringFromCharCode(_65533);
+ break;
+ }
+ else {
+ _this._state = state;
+ _this._charOrIndex = i - 1;
+ return "";
+ }
+ state = 0;
+ }
+ if (i === end)
+ break $label0$0;
+ i0 = i + 1;
+ if (!(i >= 0 && i < t1))
+ return A.ioore(bytes, i);
+ byte = bytes[i];
+ }
+ i0 = i + 1;
+ if (!(i >= 0 && i < t1))
+ return A.ioore(bytes, i);
+ byte = bytes[i];
+ if (byte < 128) {
+ while (true) {
+ if (!(i0 < end)) {
+ markEnd = end;
+ break;
+ }
+ i1 = i0 + 1;
+ if (!(i0 >= 0 && i0 < t1))
+ return A.ioore(bytes, i0);
+ byte = bytes[i0];
+ if (byte >= 128) {
+ markEnd = i1 - 1;
+ i0 = i1;
+ break;
+ }
+ i0 = i1;
+ }
+ if (markEnd - i < 20)
+ for (m = i; m < markEnd; ++m) {
+ if (!(m < t1))
+ return A.ioore(bytes, m);
+ buffer._contents += A.Primitives_stringFromCharCode(bytes[m]);
+ }
+ else
+ buffer._contents += A.String_String$fromCharCodes(bytes, i, markEnd);
+ if (markEnd === end)
+ break $label0$0;
+ i = i0;
+ } else
+ i = i0;
+ }
+ if (single && state > 32)
+ if (t2)
+ buffer._contents += A.Primitives_stringFromCharCode(_65533);
+ else {
+ _this._state = 77;
+ _this._charOrIndex = end;
+ return "";
+ }
+ _this._state = state;
+ _this._charOrIndex = char;
+ t1 = buffer._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ };
+ A.NoSuchMethodError_toString_closure.prototype = {
+ call$2(key, value) {
+ var t1, t2, t3;
+ type$.Symbol._as(key);
+ t1 = this.sb;
+ t2 = this._box_0;
+ t3 = t1._contents += t2.comma;
+ t3 += key._name;
+ t1._contents = t3;
+ t1._contents = t3 + ": ";
+ t1._contents += A.Error_safeToString(value);
+ t2.comma = ", ";
+ },
+ $signature: 39
+ };
+ A.DateTime.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.DateTime && this._core$_value === other._core$_value && true;
+ },
+ get$hashCode(_) {
+ var t1 = this._core$_value;
+ return (t1 ^ B.JSInt_methods._shrOtherPositive$1(t1, 30)) & 1073741823;
+ },
+ toString$0(_) {
+ var _this = this,
+ y = A.DateTime__fourDigits(A.Primitives_getYear(_this)),
+ m = A.DateTime__twoDigits(A.Primitives_getMonth(_this)),
+ d = A.DateTime__twoDigits(A.Primitives_getDay(_this)),
+ h = A.DateTime__twoDigits(A.Primitives_getHours(_this)),
+ min = A.DateTime__twoDigits(A.Primitives_getMinutes(_this)),
+ sec = A.DateTime__twoDigits(A.Primitives_getSeconds(_this)),
+ ms = A.DateTime__threeDigits(A.Primitives_getMilliseconds(_this));
+ return y + "-" + m + "-" + d + " " + h + ":" + min + ":" + sec + "." + ms + "Z";
+ }
+ };
+ A.Duration.prototype = {
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ return other instanceof A.Duration && this._duration === other._duration;
+ },
+ get$hashCode(_) {
+ return B.JSInt_methods.get$hashCode(this._duration);
+ },
+ toString$0(_) {
+ var minutes, minutesPadding, seconds, secondsPadding,
+ microseconds = this._duration,
+ hours = B.JSInt_methods._tdivFast$1(microseconds, 3600000000);
+ microseconds %= 3600000000;
+ minutes = B.JSInt_methods._tdivFast$1(microseconds, 60000000);
+ microseconds %= 60000000;
+ minutesPadding = minutes < 10 ? "0" : "";
+ seconds = B.JSInt_methods._tdivFast$1(microseconds, 1000000);
+ secondsPadding = seconds < 10 ? "0" : "";
+ return "" + hours + ":" + minutesPadding + minutes + ":" + secondsPadding + seconds + "." + B.JSString_methods.padLeft$2(B.JSInt_methods.toString$0(microseconds % 1000000), 6, "0");
+ }
+ };
+ A.Error.prototype = {
+ get$stackTrace() {
+ return A.getTraceFromException(this.$thrownJsError);
+ }
+ };
+ A.AssertionError.prototype = {
+ toString$0(_) {
+ var t1 = this.message;
+ if (t1 != null)
+ return "Assertion failed: " + A.Error_safeToString(t1);
+ return "Assertion failed";
+ }
+ };
+ A.TypeError.prototype = {};
+ A.ArgumentError.prototype = {
+ get$_errorName() {
+ return "Invalid argument" + (!this._hasValue ? "(s)" : "");
+ },
+ get$_errorExplanation() {
+ return "";
+ },
+ toString$0(_) {
+ var _this = this,
+ $name = _this.name,
+ nameString = $name == null ? "" : " (" + $name + ")",
+ message = _this.message,
+ messageString = message == null ? "" : ": " + A.S(message),
+ prefix = _this.get$_errorName() + nameString + messageString;
+ if (!_this._hasValue)
+ return prefix;
+ return prefix + _this.get$_errorExplanation() + ": " + A.Error_safeToString(_this.get$invalidValue());
+ },
+ get$invalidValue() {
+ return this.invalidValue;
+ }
+ };
+ A.RangeError.prototype = {
+ get$invalidValue() {
+ return A._asNumQ(this.invalidValue);
+ },
+ get$_errorName() {
+ return "RangeError";
+ },
+ get$_errorExplanation() {
+ var explanation,
+ start = this.start,
+ end = this.end;
+ if (start == null)
+ explanation = end != null ? ": Not less than or equal to " + A.S(end) : "";
+ else if (end == null)
+ explanation = ": Not greater than or equal to " + A.S(start);
+ else if (end > start)
+ explanation = ": Not in inclusive range " + A.S(start) + ".." + A.S(end);
+ else
+ explanation = end < start ? ": Valid value range is empty" : ": Only valid value is " + A.S(start);
+ return explanation;
+ }
+ };
+ A.IndexError.prototype = {
+ get$invalidValue() {
+ return A._asInt(this.invalidValue);
+ },
+ get$_errorName() {
+ return "RangeError";
+ },
+ get$_errorExplanation() {
+ if (A._asInt(this.invalidValue) < 0)
+ return ": index must not be negative";
+ var t1 = this.length;
+ if (t1 === 0)
+ return ": no indices are valid";
+ return ": index should be less than " + t1;
+ },
+ get$length(receiver) {
+ return this.length;
+ }
+ };
+ A.NoSuchMethodError.prototype = {
+ toString$0(_) {
+ var $arguments, t1, _i, t2, t3, argument, receiverText, actualParameters, _this = this, _box_0 = {},
+ sb = new A.StringBuffer("");
+ _box_0.comma = "";
+ $arguments = _this._core$_arguments;
+ for (t1 = $arguments.length, _i = 0, t2 = "", t3 = ""; _i < t1; ++_i, t3 = ", ") {
+ argument = $arguments[_i];
+ sb._contents = t2 + t3;
+ t2 = sb._contents += A.Error_safeToString(argument);
+ _box_0.comma = ", ";
+ }
+ _this._namedArguments.forEach$1(0, new A.NoSuchMethodError_toString_closure(_box_0, sb));
+ receiverText = A.Error_safeToString(_this._core$_receiver);
+ actualParameters = sb.toString$0(0);
+ return "NoSuchMethodError: method not found: '" + _this._core$_memberName._name + "'\nReceiver: " + receiverText + "\nArguments: [" + actualParameters + "]";
+ }
+ };
+ A.UnsupportedError.prototype = {
+ toString$0(_) {
+ return "Unsupported operation: " + this.message;
+ }
+ };
+ A.UnimplementedError.prototype = {
+ toString$0(_) {
+ return "UnimplementedError: " + this.message;
+ }
+ };
+ A.StateError.prototype = {
+ toString$0(_) {
+ return "Bad state: " + this.message;
+ }
+ };
+ A.ConcurrentModificationError.prototype = {
+ toString$0(_) {
+ var t1 = this.modifiedObject;
+ if (t1 == null)
+ return "Concurrent modification during iteration.";
+ return "Concurrent modification during iteration: " + A.Error_safeToString(t1) + ".";
+ }
+ };
+ A.OutOfMemoryError.prototype = {
+ toString$0(_) {
+ return "Out of Memory";
+ },
+ get$stackTrace() {
+ return null;
+ },
+ $isError: 1
+ };
+ A.StackOverflowError.prototype = {
+ toString$0(_) {
+ return "Stack Overflow";
+ },
+ get$stackTrace() {
+ return null;
+ },
+ $isError: 1
+ };
+ A._Exception.prototype = {
+ toString$0(_) {
+ return "Exception: " + this.message;
+ },
+ $isException: 1
+ };
+ A.FormatException.prototype = {
+ toString$0(_) {
+ var t1, lineEnd, lineNum, lineStart, previousCharWasCR, i, char, end, start, prefix, postfix,
+ message = this.message,
+ report = "" !== message ? "FormatException: " + message : "FormatException",
+ offset = this.offset,
+ source = this.source;
+ if (typeof source == "string") {
+ if (offset != null)
+ t1 = offset < 0 || offset > source.length;
+ else
+ t1 = false;
+ if (t1)
+ offset = null;
+ if (offset == null) {
+ if (source.length > 78)
+ source = B.JSString_methods.substring$2(source, 0, 75) + "...";
+ return report + "\n" + source;
+ }
+ for (lineEnd = source.length, lineNum = 1, lineStart = 0, previousCharWasCR = false, i = 0; i < offset; ++i) {
+ if (!(i < lineEnd))
+ return A.ioore(source, i);
+ char = source.charCodeAt(i);
+ if (char === 10) {
+ if (lineStart !== i || !previousCharWasCR)
+ ++lineNum;
+ lineStart = i + 1;
+ previousCharWasCR = false;
+ } else if (char === 13) {
+ ++lineNum;
+ lineStart = i + 1;
+ previousCharWasCR = true;
+ }
+ }
+ report = lineNum > 1 ? report + (" (at line " + lineNum + ", character " + (offset - lineStart + 1) + ")\n") : report + (" (at character " + (offset + 1) + ")\n");
+ for (i = offset; i < lineEnd; ++i) {
+ if (!(i >= 0))
+ return A.ioore(source, i);
+ char = source.charCodeAt(i);
+ if (char === 10 || char === 13) {
+ lineEnd = i;
+ break;
+ }
+ }
+ if (lineEnd - lineStart > 78)
+ if (offset - lineStart < 75) {
+ end = lineStart + 75;
+ start = lineStart;
+ prefix = "";
+ postfix = "...";
+ } else {
+ if (lineEnd - offset < 75) {
+ start = lineEnd - 75;
+ end = lineEnd;
+ postfix = "";
+ } else {
+ start = offset - 36;
+ end = offset + 36;
+ postfix = "...";
+ }
+ prefix = "...";
+ }
+ else {
+ end = lineEnd;
+ start = lineStart;
+ prefix = "";
+ postfix = "";
+ }
+ return report + prefix + B.JSString_methods.substring$2(source, start, end) + postfix + "\n" + B.JSString_methods.$mul(" ", offset - start + prefix.length) + "^\n";
+ } else
+ return offset != null ? report + (" (at offset " + A.S(offset) + ")") : report;
+ },
+ $isException: 1
+ };
+ A.Iterable.prototype = {
+ cast$1$0(_, $R) {
+ return A.CastIterable_CastIterable(this, A._instanceType(this)._eval$1("Iterable.E"), $R);
+ },
+ map$1$1(_, toElement, $T) {
+ var t1 = A._instanceType(this);
+ return A.MappedIterable_MappedIterable(this, t1._bind$1($T)._eval$1("1(Iterable.E)")._as(toElement), t1._eval$1("Iterable.E"), $T);
+ },
+ toList$1$growable(_, growable) {
+ return A.List_List$of(this, growable, A._instanceType(this)._eval$1("Iterable.E"));
+ },
+ toList$0($receiver) {
+ return this.toList$1$growable($receiver, true);
+ },
+ get$length(_) {
+ var count,
+ it = this.get$iterator(this);
+ for (count = 0; it.moveNext$0();)
+ ++count;
+ return count;
+ },
+ get$isEmpty(_) {
+ return !this.get$iterator(this).moveNext$0();
+ },
+ get$isNotEmpty(_) {
+ return !this.get$isEmpty(this);
+ },
+ skip$1(_, count) {
+ return A.SkipIterable_SkipIterable(this, count, A._instanceType(this)._eval$1("Iterable.E"));
+ },
+ skipWhile$1(_, test) {
+ var t1 = A._instanceType(this);
+ return new A.SkipWhileIterable(this, t1._eval$1("bool(Iterable.E)")._as(test), t1._eval$1("SkipWhileIterable<Iterable.E>"));
+ },
+ get$first(_) {
+ var it = this.get$iterator(this);
+ if (!it.moveNext$0())
+ throw A.wrapException(A.IterableElementError_noElement());
+ return it.get$current(it);
+ },
+ get$last(_) {
+ var result,
+ it = this.get$iterator(this);
+ if (!it.moveNext$0())
+ throw A.wrapException(A.IterableElementError_noElement());
+ do
+ result = it.get$current(it);
+ while (it.moveNext$0());
+ return result;
+ },
+ elementAt$1(_, index) {
+ var iterator, skipCount;
+ A.RangeError_checkNotNegative(index, "index");
+ iterator = this.get$iterator(this);
+ for (skipCount = index; iterator.moveNext$0();) {
+ if (skipCount === 0)
+ return iterator.get$current(iterator);
+ --skipCount;
+ }
+ throw A.wrapException(A.IndexError$withLength(index, index - skipCount, this, "index"));
+ },
+ toString$0(_) {
+ return A.Iterable_iterableToShortString(this, "(", ")");
+ }
+ };
+ A.Null.prototype = {
+ get$hashCode(_) {
+ return A.Object.prototype.get$hashCode.call(this, this);
+ },
+ toString$0(_) {
+ return "null";
+ }
+ };
+ A.Object.prototype = {$isObject: 1,
+ $eq(_, other) {
+ return this === other;
+ },
+ get$hashCode(_) {
+ return A.Primitives_objectHashCode(this);
+ },
+ toString$0(_) {
+ return "Instance of '" + A.Primitives_objectTypeName(this) + "'";
+ },
+ noSuchMethod$1(_, invocation) {
+ throw A.wrapException(A.NoSuchMethodError_NoSuchMethodError$withInvocation(this, type$.Invocation._as(invocation)));
+ },
+ get$runtimeType(_) {
+ return A.getRuntimeTypeOfDartObject(this);
+ },
+ toString() {
+ return this.toString$0(this);
+ }
+ };
+ A._StringStackTrace.prototype = {
+ toString$0(_) {
+ return this._stackTrace;
+ },
+ $isStackTrace: 1
+ };
+ A.StringBuffer.prototype = {
+ get$length(_) {
+ return this._contents.length;
+ },
+ write$1(_, obj) {
+ this._contents += A.S(obj);
+ },
+ writeCharCode$1(charCode) {
+ this._contents += A.Primitives_stringFromCharCode(charCode);
+ },
+ toString$0(_) {
+ var t1 = this._contents;
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ $isStringSink: 1
+ };
+ A.Uri_splitQueryString_closure.prototype = {
+ call$2(map, element) {
+ var index, key, value, t1;
+ type$.Map_String_String._as(map);
+ A._asString(element);
+ index = B.JSString_methods.indexOf$1(element, "=");
+ if (index === -1) {
+ if (element !== "")
+ J.$indexSet$ax(map, A._Uri__uriDecode(element, 0, element.length, this.encoding, true), "");
+ } else if (index !== 0) {
+ key = B.JSString_methods.substring$2(element, 0, index);
+ value = B.JSString_methods.substring$1(element, index + 1);
+ t1 = this.encoding;
+ J.$indexSet$ax(map, A._Uri__uriDecode(key, 0, key.length, t1, true), A._Uri__uriDecode(value, 0, value.length, t1, true));
+ }
+ return map;
+ },
+ $signature: 40
+ };
+ A.Uri__parseIPv4Address_error.prototype = {
+ call$2(msg, position) {
+ throw A.wrapException(A.FormatException$("Illegal IPv4 address, " + msg, this.host, position));
+ },
+ $signature: 25
+ };
+ A.Uri_parseIPv6Address_error.prototype = {
+ call$2(msg, position) {
+ throw A.wrapException(A.FormatException$("Illegal IPv6 address, " + msg, this.host, position));
+ },
+ $signature: 45
+ };
+ A.Uri_parseIPv6Address_parseHex.prototype = {
+ call$2(start, end) {
+ var value;
+ if (end - start > 4)
+ this.error.call$2("an IPv6 part can only contain a maximum of 4 hex digits", start);
+ value = A.int_parse(B.JSString_methods.substring$2(this.host, start, end), 16);
+ if (value < 0 || value > 65535)
+ this.error.call$2("each part must be in the range of `0x0..0xFFFF`", start);
+ return value;
+ },
+ $signature: 46
+ };
+ A._Uri.prototype = {
+ get$_text() {
+ var t1, t2, t3, t4, _this = this,
+ value = _this.___Uri__text_FI;
+ if (value === $) {
+ t1 = _this.scheme;
+ t2 = t1.length !== 0 ? "" + t1 + ":" : "";
+ t3 = _this._host;
+ t4 = t3 == null;
+ if (!t4 || t1 === "file") {
+ t1 = t2 + "//";
+ t2 = _this._userInfo;
+ if (t2.length !== 0)
+ t1 = t1 + t2 + "@";
+ if (!t4)
+ t1 += t3;
+ t2 = _this._port;
+ if (t2 != null)
+ t1 = t1 + ":" + A.S(t2);
+ } else
+ t1 = t2;
+ t1 += _this.path;
+ t2 = _this._query;
+ if (t2 != null)
+ t1 = t1 + "?" + t2;
+ t2 = _this._fragment;
+ if (t2 != null)
+ t1 = t1 + "#" + t2;
+ value !== $ && A.throwLateFieldADI("_text");
+ value = _this.___Uri__text_FI = t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ return value;
+ },
+ get$pathSegments() {
+ var pathToSplit, t1, result, _this = this,
+ value = _this.___Uri_pathSegments_FI;
+ if (value === $) {
+ pathToSplit = _this.path;
+ t1 = pathToSplit.length;
+ if (t1 !== 0) {
+ if (0 >= t1)
+ return A.ioore(pathToSplit, 0);
+ t1 = pathToSplit.charCodeAt(0) === 47;
+ } else
+ t1 = false;
+ if (t1)
+ pathToSplit = B.JSString_methods.substring$1(pathToSplit, 1);
+ result = pathToSplit.length === 0 ? B.List_empty : A.List_List$unmodifiable(new A.MappedListIterable(A._setArrayType(pathToSplit.split("/"), type$.JSArray_String), type$.dynamic_Function_String._as(A.core_Uri_decodeComponent$closure()), type$.MappedListIterable_String_dynamic), type$.String);
+ _this.___Uri_pathSegments_FI !== $ && A.throwLateFieldADI("pathSegments");
+ _this.set$___Uri_pathSegments_FI(result);
+ value = result;
+ }
+ return value;
+ },
+ get$hashCode(_) {
+ var result, _this = this,
+ value = _this.___Uri_hashCode_FI;
+ if (value === $) {
+ result = B.JSString_methods.get$hashCode(_this.get$_text());
+ _this.___Uri_hashCode_FI !== $ && A.throwLateFieldADI("hashCode");
+ _this.___Uri_hashCode_FI = result;
+ value = result;
+ }
+ return value;
+ },
+ get$queryParameters() {
+ var t1, result, _this = this,
+ value = _this.___Uri_queryParameters_FI;
+ if (value === $) {
+ t1 = _this._query;
+ result = new A.UnmodifiableMapView(A.Uri_splitQueryString(t1 == null ? "" : t1), type$.UnmodifiableMapView_String_String);
+ _this.___Uri_queryParameters_FI !== $ && A.throwLateFieldADI("queryParameters");
+ _this.set$___Uri_queryParameters_FI(result);
+ value = result;
+ }
+ return value;
+ },
+ get$userInfo() {
+ return this._userInfo;
+ },
+ get$host(_) {
+ var host = this._host;
+ if (host == null)
+ return "";
+ if (B.JSString_methods.startsWith$1(host, "["))
+ return B.JSString_methods.substring$2(host, 1, host.length - 1);
+ return host;
+ },
+ get$port(_) {
+ var t1 = this._port;
+ return t1 == null ? A._Uri__defaultPort(this.scheme) : t1;
+ },
+ get$query(_) {
+ var t1 = this._query;
+ return t1 == null ? "" : t1;
+ },
+ get$fragment() {
+ var t1 = this._fragment;
+ return t1 == null ? "" : t1;
+ },
+ isScheme$1(scheme) {
+ var thisScheme = this.scheme;
+ if (scheme.length !== thisScheme.length)
+ return false;
+ return A._caseInsensitiveCompareStart(scheme, thisScheme, 0) >= 0;
+ },
+ removeFragment$0() {
+ var _this = this;
+ if (_this._fragment == null)
+ return _this;
+ return A._Uri$_internal(_this.scheme, _this._userInfo, _this._host, _this._port, _this.path, _this._query, null);
+ },
+ _mergePaths$2(base, reference) {
+ var backCount, refStart, baseEnd, t1, newEnd, delta, t2, t3;
+ for (backCount = 0, refStart = 0; B.JSString_methods.startsWith$2(reference, "../", refStart);) {
+ refStart += 3;
+ ++backCount;
+ }
+ baseEnd = B.JSString_methods.lastIndexOf$1(base, "/");
+ t1 = base.length;
+ while (true) {
+ if (!(baseEnd > 0 && backCount > 0))
+ break;
+ newEnd = B.JSString_methods.lastIndexOf$2(base, "/", baseEnd - 1);
+ if (newEnd < 0)
+ break;
+ delta = baseEnd - newEnd;
+ t2 = delta !== 2;
+ if (!t2 || delta === 3) {
+ t3 = newEnd + 1;
+ if (!(t3 < t1))
+ return A.ioore(base, t3);
+ if (base.charCodeAt(t3) === 46)
+ if (t2) {
+ t2 = newEnd + 2;
+ if (!(t2 < t1))
+ return A.ioore(base, t2);
+ t2 = base.charCodeAt(t2) === 46;
+ } else
+ t2 = true;
+ else
+ t2 = false;
+ } else
+ t2 = false;
+ if (t2)
+ break;
+ --backCount;
+ baseEnd = newEnd;
+ }
+ return B.JSString_methods.replaceRange$3(base, baseEnd + 1, null, B.JSString_methods.substring$1(reference, refStart - 3 * backCount));
+ },
+ resolve$1(reference) {
+ return this.resolveUri$1(A.Uri_parse(reference));
+ },
+ resolveUri$1(reference) {
+ var targetScheme, targetUserInfo, targetHost, targetPort, targetPath, targetQuery, packageNameEnd, packageName, mergedPath, t1, _this = this, _null = null;
+ if (reference.get$scheme().length !== 0) {
+ targetScheme = reference.get$scheme();
+ if (reference.get$hasAuthority()) {
+ targetUserInfo = reference.get$userInfo();
+ targetHost = reference.get$host(reference);
+ targetPort = reference.get$hasPort() ? reference.get$port(reference) : _null;
+ } else {
+ targetPort = _null;
+ targetHost = targetPort;
+ targetUserInfo = "";
+ }
+ targetPath = A._Uri__removeDotSegments(reference.get$path(reference));
+ targetQuery = reference.get$hasQuery() ? reference.get$query(reference) : _null;
+ } else {
+ targetScheme = _this.scheme;
+ if (reference.get$hasAuthority()) {
+ targetUserInfo = reference.get$userInfo();
+ targetHost = reference.get$host(reference);
+ targetPort = A._Uri__makePort(reference.get$hasPort() ? reference.get$port(reference) : _null, targetScheme);
+ targetPath = A._Uri__removeDotSegments(reference.get$path(reference));
+ targetQuery = reference.get$hasQuery() ? reference.get$query(reference) : _null;
+ } else {
+ targetUserInfo = _this._userInfo;
+ targetHost = _this._host;
+ targetPort = _this._port;
+ targetPath = _this.path;
+ if (reference.get$path(reference) === "")
+ targetQuery = reference.get$hasQuery() ? reference.get$query(reference) : _this._query;
+ else {
+ packageNameEnd = A._Uri__packageNameEnd(_this, targetPath);
+ if (packageNameEnd > 0) {
+ packageName = B.JSString_methods.substring$2(targetPath, 0, packageNameEnd);
+ targetPath = reference.get$hasAbsolutePath() ? packageName + A._Uri__removeDotSegments(reference.get$path(reference)) : packageName + A._Uri__removeDotSegments(_this._mergePaths$2(B.JSString_methods.substring$1(targetPath, packageName.length), reference.get$path(reference)));
+ } else if (reference.get$hasAbsolutePath())
+ targetPath = A._Uri__removeDotSegments(reference.get$path(reference));
+ else if (targetPath.length === 0)
+ if (targetHost == null)
+ targetPath = targetScheme.length === 0 ? reference.get$path(reference) : A._Uri__removeDotSegments(reference.get$path(reference));
+ else
+ targetPath = A._Uri__removeDotSegments("/" + reference.get$path(reference));
+ else {
+ mergedPath = _this._mergePaths$2(targetPath, reference.get$path(reference));
+ t1 = targetScheme.length === 0;
+ if (!t1 || targetHost != null || B.JSString_methods.startsWith$1(targetPath, "/"))
+ targetPath = A._Uri__removeDotSegments(mergedPath);
+ else
+ targetPath = A._Uri__normalizeRelativePath(mergedPath, !t1 || targetHost != null);
+ }
+ targetQuery = reference.get$hasQuery() ? reference.get$query(reference) : _null;
+ }
+ }
+ }
+ return A._Uri$_internal(targetScheme, targetUserInfo, targetHost, targetPort, targetPath, targetQuery, reference.get$hasFragment() ? reference.get$fragment() : _null);
+ },
+ get$hasAuthority() {
+ return this._host != null;
+ },
+ get$hasPort() {
+ return this._port != null;
+ },
+ get$hasQuery() {
+ return this._query != null;
+ },
+ get$hasFragment() {
+ return this._fragment != null;
+ },
+ get$hasAbsolutePath() {
+ return B.JSString_methods.startsWith$1(this.path, "/");
+ },
+ toFilePath$0() {
+ var pathSegments, _this = this,
+ t1 = _this.scheme;
+ if (t1 !== "" && t1 !== "file")
+ throw A.wrapException(A.UnsupportedError$("Cannot extract a file path from a " + t1 + " URI"));
+ t1 = _this._query;
+ if ((t1 == null ? "" : t1) !== "")
+ throw A.wrapException(A.UnsupportedError$(string$.Cannotfq));
+ t1 = _this._fragment;
+ if ((t1 == null ? "" : t1) !== "")
+ throw A.wrapException(A.UnsupportedError$(string$.Cannotff));
+ t1 = $.$get$_Uri__isWindowsCached();
+ if (t1)
+ t1 = A._Uri__toWindowsFilePath(_this);
+ else {
+ if (_this._host != null && _this.get$host(_this) !== "")
+ A.throwExpression(A.UnsupportedError$(string$.Cannotn));
+ pathSegments = _this.get$pathSegments();
+ A._Uri__checkNonWindowsPathReservedCharacters(pathSegments, false);
+ t1 = A.StringBuffer__writeAll(B.JSString_methods.startsWith$1(_this.path, "/") ? "" + "/" : "", pathSegments, "/");
+ t1 = t1.charCodeAt(0) == 0 ? t1 : t1;
+ }
+ return t1;
+ },
+ toString$0(_) {
+ return this.get$_text();
+ },
+ $eq(_, other) {
+ var t1, t2, _this = this;
+ if (other == null)
+ return false;
+ if (_this === other)
+ return true;
+ if (type$.Uri._is(other))
+ if (_this.scheme === other.get$scheme())
+ if (_this._host != null === other.get$hasAuthority())
+ if (_this._userInfo === other.get$userInfo())
+ if (_this.get$host(_this) === other.get$host(other))
+ if (_this.get$port(_this) === other.get$port(other))
+ if (_this.path === other.get$path(other)) {
+ t1 = _this._query;
+ t2 = t1 == null;
+ if (!t2 === other.get$hasQuery()) {
+ if (t2)
+ t1 = "";
+ if (t1 === other.get$query(other)) {
+ t1 = _this._fragment;
+ t2 = t1 == null;
+ if (!t2 === other.get$hasFragment()) {
+ if (t2)
+ t1 = "";
+ t1 = t1 === other.get$fragment();
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ else
+ t1 = false;
+ return t1;
+ },
+ set$___Uri_pathSegments_FI(___Uri_pathSegments_FI) {
+ this.___Uri_pathSegments_FI = type$.List_String._as(___Uri_pathSegments_FI);
+ },
+ set$___Uri_queryParameters_FI(___Uri_queryParameters_FI) {
+ this.___Uri_queryParameters_FI = type$.Map_String_String._as(___Uri_queryParameters_FI);
+ },
+ $isUri: 1,
+ get$scheme() {
+ return this.scheme;
+ },
+ get$path(receiver) {
+ return this.path;
+ }
+ };
+ A._Uri__makePath_closure.prototype = {
+ call$1(s) {
+ return A._Uri__uriEncode(B.List_XRg0, A._asString(s), B.C_Utf8Codec, false);
+ },
+ $signature: 17
+ };
+ A.UriData.prototype = {
+ get$uri() {
+ var t2, queryIndex, end, query, _this = this, _null = null,
+ t1 = _this._uriCache;
+ if (t1 == null) {
+ t1 = _this._separatorIndices;
+ if (0 >= t1.length)
+ return A.ioore(t1, 0);
+ t2 = _this._text;
+ t1 = t1[0] + 1;
+ queryIndex = B.JSString_methods.indexOf$2(t2, "?", t1);
+ end = t2.length;
+ if (queryIndex >= 0) {
+ query = A._Uri__normalizeOrSubstring(t2, queryIndex + 1, end, B.List_oFp, false, false);
+ end = queryIndex;
+ } else
+ query = _null;
+ t1 = _this._uriCache = new A._DataUri("data", "", _null, _null, A._Uri__normalizeOrSubstring(t2, t1, end, B.List_XRg, false, false), query, _null);
+ }
+ return t1;
+ },
+ toString$0(_) {
+ var t2,
+ t1 = this._separatorIndices;
+ if (0 >= t1.length)
+ return A.ioore(t1, 0);
+ t2 = this._text;
+ return t1[0] === -1 ? "data:" + t2 : t2;
+ }
+ };
+ A._createTables_build.prototype = {
+ call$2(state, defaultTransition) {
+ var t1 = this.tables;
+ if (!(state < t1.length))
+ return A.ioore(t1, state);
+ t1 = t1[state];
+ B.NativeUint8List_methods.fillRange$3(t1, 0, 96, defaultTransition);
+ return t1;
+ },
+ $signature: 61
+ };
+ A._createTables_setChars.prototype = {
+ call$3(target, chars, transition) {
+ var t1, i, t2;
+ for (t1 = chars.length, i = 0; i < t1; ++i) {
+ t2 = chars.charCodeAt(i) ^ 96;
+ if (!(t2 < 96))
+ return A.ioore(target, t2);
+ target[t2] = transition;
+ }
+ },
+ $signature: 18
+ };
+ A._createTables_setRange.prototype = {
+ call$3(target, range, transition) {
+ var i, n,
+ t1 = range.length;
+ if (0 >= t1)
+ return A.ioore(range, 0);
+ i = range.charCodeAt(0);
+ if (1 >= t1)
+ return A.ioore(range, 1);
+ n = range.charCodeAt(1);
+ for (; i <= n; ++i) {
+ t1 = (i ^ 96) >>> 0;
+ if (!(t1 < 96))
+ return A.ioore(target, t1);
+ target[t1] = transition;
+ }
+ },
+ $signature: 18
+ };
+ A._SimpleUri.prototype = {
+ get$hasAuthority() {
+ return this._hostStart > 0;
+ },
+ get$hasPort() {
+ return this._hostStart > 0 && this._portStart + 1 < this._pathStart;
+ },
+ get$hasQuery() {
+ return this._queryStart < this._fragmentStart;
+ },
+ get$hasFragment() {
+ return this._fragmentStart < this._uri.length;
+ },
+ get$hasAbsolutePath() {
+ return B.JSString_methods.startsWith$2(this._uri, "/", this._pathStart);
+ },
+ get$scheme() {
+ var t1 = this._schemeCache;
+ return t1 == null ? this._schemeCache = this._computeScheme$0() : t1;
+ },
+ _computeScheme$0() {
+ var t2, _this = this,
+ t1 = _this._schemeEnd;
+ if (t1 <= 0)
+ return "";
+ t2 = t1 === 4;
+ if (t2 && B.JSString_methods.startsWith$1(_this._uri, "http"))
+ return "http";
+ if (t1 === 5 && B.JSString_methods.startsWith$1(_this._uri, "https"))
+ return "https";
+ if (t2 && B.JSString_methods.startsWith$1(_this._uri, "file"))
+ return "file";
+ if (t1 === 7 && B.JSString_methods.startsWith$1(_this._uri, "package"))
+ return "package";
+ return B.JSString_methods.substring$2(_this._uri, 0, t1);
+ },
+ get$userInfo() {
+ var t1 = this._hostStart,
+ t2 = this._schemeEnd + 3;
+ return t1 > t2 ? B.JSString_methods.substring$2(this._uri, t2, t1 - 1) : "";
+ },
+ get$host(_) {
+ var t1 = this._hostStart;
+ return t1 > 0 ? B.JSString_methods.substring$2(this._uri, t1, this._portStart) : "";
+ },
+ get$port(_) {
+ var t1, _this = this;
+ if (_this.get$hasPort())
+ return A.int_parse(B.JSString_methods.substring$2(_this._uri, _this._portStart + 1, _this._pathStart), null);
+ t1 = _this._schemeEnd;
+ if (t1 === 4 && B.JSString_methods.startsWith$1(_this._uri, "http"))
+ return 80;
+ if (t1 === 5 && B.JSString_methods.startsWith$1(_this._uri, "https"))
+ return 443;
+ return 0;
+ },
+ get$path(_) {
+ return B.JSString_methods.substring$2(this._uri, this._pathStart, this._queryStart);
+ },
+ get$query(_) {
+ var t1 = this._queryStart,
+ t2 = this._fragmentStart;
+ return t1 < t2 ? B.JSString_methods.substring$2(this._uri, t1 + 1, t2) : "";
+ },
+ get$fragment() {
+ var t1 = this._fragmentStart,
+ t2 = this._uri;
+ return t1 < t2.length ? B.JSString_methods.substring$1(t2, t1 + 1) : "";
+ },
+ get$pathSegments() {
+ var parts, t2, i,
+ start = this._pathStart,
+ end = this._queryStart,
+ t1 = this._uri;
+ if (B.JSString_methods.startsWith$2(t1, "/", start))
+ ++start;
+ if (start === end)
+ return B.List_empty;
+ parts = A._setArrayType([], type$.JSArray_String);
+ for (t2 = t1.length, i = start; i < end; ++i) {
+ if (!(i >= 0 && i < t2))
+ return A.ioore(t1, i);
+ if (t1.charCodeAt(i) === 47) {
+ B.JSArray_methods.add$1(parts, B.JSString_methods.substring$2(t1, start, i));
+ start = i + 1;
+ }
+ }
+ B.JSArray_methods.add$1(parts, B.JSString_methods.substring$2(t1, start, end));
+ return A.List_List$unmodifiable(parts, type$.String);
+ },
+ get$queryParameters() {
+ var _this = this;
+ if (_this._queryStart >= _this._fragmentStart)
+ return B.Map_empty;
+ return new A.UnmodifiableMapView(A.Uri_splitQueryString(_this.get$query(_this)), type$.UnmodifiableMapView_String_String);
+ },
+ _isPort$1(port) {
+ var portDigitStart = this._portStart + 1;
+ return portDigitStart + port.length === this._pathStart && B.JSString_methods.startsWith$2(this._uri, port, portDigitStart);
+ },
+ removeFragment$0() {
+ var _this = this,
+ t1 = _this._fragmentStart,
+ t2 = _this._uri;
+ if (t1 >= t2.length)
+ return _this;
+ return new A._SimpleUri(B.JSString_methods.substring$2(t2, 0, t1), _this._schemeEnd, _this._hostStart, _this._portStart, _this._pathStart, _this._queryStart, t1, _this._schemeCache);
+ },
+ resolve$1(reference) {
+ return this.resolveUri$1(A.Uri_parse(reference));
+ },
+ resolveUri$1(reference) {
+ if (reference instanceof A._SimpleUri)
+ return this._simpleMerge$2(this, reference);
+ return this._toNonSimple$0().resolveUri$1(reference);
+ },
+ _simpleMerge$2(base, ref) {
+ var t2, t3, t4, isSimple, delta, refStart, basePathStart, packageNameEnd, basePathStart0, baseStart, baseEnd, baseUri, baseStart0, backCount, refStart0, insert,
+ t1 = ref._schemeEnd;
+ if (t1 > 0)
+ return ref;
+ t2 = ref._hostStart;
+ if (t2 > 0) {
+ t3 = base._schemeEnd;
+ if (t3 <= 0)
+ return ref;
+ t4 = t3 === 4;
+ if (t4 && B.JSString_methods.startsWith$1(base._uri, "file"))
+ isSimple = ref._pathStart !== ref._queryStart;
+ else if (t4 && B.JSString_methods.startsWith$1(base._uri, "http"))
+ isSimple = !ref._isPort$1("80");
+ else
+ isSimple = !(t3 === 5 && B.JSString_methods.startsWith$1(base._uri, "https")) || !ref._isPort$1("443");
+ if (isSimple) {
+ delta = t3 + 1;
+ return new A._SimpleUri(B.JSString_methods.substring$2(base._uri, 0, delta) + B.JSString_methods.substring$1(ref._uri, t1 + 1), t3, t2 + delta, ref._portStart + delta, ref._pathStart + delta, ref._queryStart + delta, ref._fragmentStart + delta, base._schemeCache);
+ } else
+ return this._toNonSimple$0().resolveUri$1(ref);
+ }
+ refStart = ref._pathStart;
+ t1 = ref._queryStart;
+ if (refStart === t1) {
+ t2 = ref._fragmentStart;
+ if (t1 < t2) {
+ t3 = base._queryStart;
+ delta = t3 - t1;
+ return new A._SimpleUri(B.JSString_methods.substring$2(base._uri, 0, t3) + B.JSString_methods.substring$1(ref._uri, t1), base._schemeEnd, base._hostStart, base._portStart, base._pathStart, t1 + delta, t2 + delta, base._schemeCache);
+ }
+ t1 = ref._uri;
+ if (t2 < t1.length) {
+ t3 = base._fragmentStart;
+ return new A._SimpleUri(B.JSString_methods.substring$2(base._uri, 0, t3) + B.JSString_methods.substring$1(t1, t2), base._schemeEnd, base._hostStart, base._portStart, base._pathStart, base._queryStart, t2 + (t3 - t2), base._schemeCache);
+ }
+ return base.removeFragment$0();
+ }
+ t2 = ref._uri;
+ if (B.JSString_methods.startsWith$2(t2, "/", refStart)) {
+ basePathStart = base._pathStart;
+ packageNameEnd = A._SimpleUri__packageNameEnd(this);
+ basePathStart0 = packageNameEnd > 0 ? packageNameEnd : basePathStart;
+ delta = basePathStart0 - refStart;
+ return new A._SimpleUri(B.JSString_methods.substring$2(base._uri, 0, basePathStart0) + B.JSString_methods.substring$1(t2, refStart), base._schemeEnd, base._hostStart, base._portStart, basePathStart, t1 + delta, ref._fragmentStart + delta, base._schemeCache);
+ }
+ baseStart = base._pathStart;
+ baseEnd = base._queryStart;
+ if (baseStart === baseEnd && base._hostStart > 0) {
+ for (; B.JSString_methods.startsWith$2(t2, "../", refStart);)
+ refStart += 3;
+ delta = baseStart - refStart + 1;
+ return new A._SimpleUri(B.JSString_methods.substring$2(base._uri, 0, baseStart) + "/" + B.JSString_methods.substring$1(t2, refStart), base._schemeEnd, base._hostStart, base._portStart, baseStart, t1 + delta, ref._fragmentStart + delta, base._schemeCache);
+ }
+ baseUri = base._uri;
+ packageNameEnd = A._SimpleUri__packageNameEnd(this);
+ if (packageNameEnd >= 0)
+ baseStart0 = packageNameEnd;
+ else
+ for (baseStart0 = baseStart; B.JSString_methods.startsWith$2(baseUri, "../", baseStart0);)
+ baseStart0 += 3;
+ backCount = 0;
+ while (true) {
+ refStart0 = refStart + 3;
+ if (!(refStart0 <= t1 && B.JSString_methods.startsWith$2(t2, "../", refStart)))
+ break;
+ ++backCount;
+ refStart = refStart0;
+ }
+ for (t3 = baseUri.length, insert = ""; baseEnd > baseStart0;) {
+ --baseEnd;
+ if (!(baseEnd >= 0 && baseEnd < t3))
+ return A.ioore(baseUri, baseEnd);
+ if (baseUri.charCodeAt(baseEnd) === 47) {
+ if (backCount === 0) {
+ insert = "/";
+ break;
+ }
+ --backCount;
+ insert = "/";
+ }
+ }
+ if (baseEnd === baseStart0 && base._schemeEnd <= 0 && !B.JSString_methods.startsWith$2(baseUri, "/", baseStart)) {
+ refStart -= backCount * 3;
+ insert = "";
+ }
+ delta = baseEnd - refStart + insert.length;
+ return new A._SimpleUri(B.JSString_methods.substring$2(baseUri, 0, baseEnd) + insert + B.JSString_methods.substring$1(t2, refStart), base._schemeEnd, base._hostStart, base._portStart, baseStart, t1 + delta, ref._fragmentStart + delta, base._schemeCache);
+ },
+ toFilePath$0() {
+ var t2, t3, _this = this,
+ t1 = _this._schemeEnd;
+ if (t1 >= 0) {
+ t2 = !(t1 === 4 && B.JSString_methods.startsWith$1(_this._uri, "file"));
+ t1 = t2;
+ } else
+ t1 = false;
+ if (t1)
+ throw A.wrapException(A.UnsupportedError$("Cannot extract a file path from a " + _this.get$scheme() + " URI"));
+ t1 = _this._queryStart;
+ t2 = _this._uri;
+ if (t1 < t2.length) {
+ if (t1 < _this._fragmentStart)
+ throw A.wrapException(A.UnsupportedError$(string$.Cannotfq));
+ throw A.wrapException(A.UnsupportedError$(string$.Cannotff));
+ }
+ t3 = $.$get$_Uri__isWindowsCached();
+ if (t3)
+ t1 = A._Uri__toWindowsFilePath(_this);
+ else {
+ if (_this._hostStart < _this._portStart)
+ A.throwExpression(A.UnsupportedError$(string$.Cannotn));
+ t1 = B.JSString_methods.substring$2(t2, _this._pathStart, t1);
+ }
+ return t1;
+ },
+ get$hashCode(_) {
+ var t1 = this._hashCodeCache;
+ return t1 == null ? this._hashCodeCache = B.JSString_methods.get$hashCode(this._uri) : t1;
+ },
+ $eq(_, other) {
+ if (other == null)
+ return false;
+ if (this === other)
+ return true;
+ return type$.Uri._is(other) && this._uri === other.toString$0(0);
+ },
+ _toNonSimple$0() {
+ var _this = this, _null = null,
+ t1 = _this.get$scheme(),
+ t2 = _this.get$userInfo(),
+ t3 = _this._hostStart > 0 ? _this.get$host(_this) : _null,
+ t4 = _this.get$hasPort() ? _this.get$port(_this) : _null,
+ t5 = _this._uri,
+ t6 = _this._queryStart,
+ t7 = B.JSString_methods.substring$2(t5, _this._pathStart, t6),
+ t8 = _this._fragmentStart;
+ t6 = t6 < t8 ? _this.get$query(_this) : _null;
+ return A._Uri$_internal(t1, t2, t3, t4, t7, t6, t8 < t5.length ? _this.get$fragment() : _null);
+ },
+ toString$0(_) {
+ return this._uri;
+ },
+ $isUri: 1
+ };
+ A._DataUri.prototype = {};
+ A.HtmlElement.prototype = {};
+ A.AccessibleNodeList.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.AnchorElement.prototype = {
+ toString$0(receiver) {
+ var t1 = String(receiver);
+ t1.toString;
+ return t1;
+ }
+ };
+ A.AreaElement.prototype = {
+ toString$0(receiver) {
+ var t1 = String(receiver);
+ t1.toString;
+ return t1;
+ }
+ };
+ A.Blob.prototype = {};
+ A.CharacterData.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.CssPerspective.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.CssRule.prototype = {$isCssRule: 1};
+ A.CssStyleDeclaration.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ }
+ };
+ A.CssStyleDeclarationBase.prototype = {};
+ A.CssStyleValue.prototype = {};
+ A.CssTransformComponent.prototype = {};
+ A.CssTransformValue.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.CssUnparsedValue.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.DataTransferItemList.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.DomException.prototype = {
+ toString$0(receiver) {
+ var t1 = String(receiver);
+ t1.toString;
+ return t1;
+ }
+ };
+ A.DomRectList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Rectangle_num._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.DomRectReadOnly.prototype = {
+ toString$0(receiver) {
+ var t2,
+ t1 = receiver.left;
+ t1.toString;
+ t2 = receiver.top;
+ t2.toString;
+ return "Rectangle (" + A.S(t1) + ", " + A.S(t2) + ") " + A.S(this.get$width(receiver)) + " x " + A.S(this.get$height(receiver));
+ },
+ $eq(receiver, other) {
+ var t1, t2;
+ if (other == null)
+ return false;
+ if (type$.Rectangle_num._is(other)) {
+ t1 = receiver.left;
+ t1.toString;
+ t2 = other.left;
+ t2.toString;
+ if (t1 === t2) {
+ t1 = receiver.top;
+ t1.toString;
+ t2 = other.top;
+ t2.toString;
+ if (t1 === t2) {
+ t1 = J.getInterceptor$x(other);
+ t1 = this.get$width(receiver) === t1.get$width(other) && this.get$height(receiver) === t1.get$height(other);
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ return t1;
+ },
+ get$hashCode(receiver) {
+ var t2,
+ t1 = receiver.left;
+ t1.toString;
+ t2 = receiver.top;
+ t2.toString;
+ return A.Object_hash(t1, t2, this.get$width(receiver), this.get$height(receiver));
+ },
+ get$_height(receiver) {
+ return receiver.height;
+ },
+ get$height(receiver) {
+ var t1 = this.get$_height(receiver);
+ t1.toString;
+ return t1;
+ },
+ get$_width(receiver) {
+ return receiver.width;
+ },
+ get$width(receiver) {
+ var t1 = this.get$_width(receiver);
+ t1.toString;
+ return t1;
+ },
+ $isRectangle: 1
+ };
+ A.DomStringList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ A._asString(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.DomTokenList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ }
+ };
+ A.Element.prototype = {
+ toString$0(receiver) {
+ var t1 = receiver.localName;
+ t1.toString;
+ return t1;
+ }
+ };
+ A.EventTarget.prototype = {};
+ A.File.prototype = {$isFile: 1};
+ A.FileList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.File._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.FileWriter.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.FormElement.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.Gamepad.prototype = {$isGamepad: 1};
+ A.History.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ }
+ };
+ A.HtmlCollection.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Node._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.Location.prototype = {
+ toString$0(receiver) {
+ var t1 = String(receiver);
+ t1.toString;
+ return t1;
+ }
+ };
+ A.MediaList.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.MidiInputMap.prototype = {
+ containsKey$1(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(key)) != null;
+ },
+ $index(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(A._asString(key)));
+ },
+ forEach$1(receiver, f) {
+ var entries, entry, t1;
+ type$.void_Function_String_dynamic._as(f);
+ entries = receiver.entries();
+ for (; true;) {
+ entry = entries.next();
+ t1 = entry.done;
+ t1.toString;
+ if (t1)
+ return;
+ t1 = entry.value[0];
+ t1.toString;
+ f.call$2(t1, A.convertNativeToDart_Dictionary(entry.value[1]));
+ }
+ },
+ get$keys(receiver) {
+ var keys = A._setArrayType([], type$.JSArray_String);
+ this.forEach$1(receiver, new A.MidiInputMap_keys_closure(keys));
+ return keys;
+ },
+ get$length(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1;
+ },
+ get$isEmpty(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1 === 0;
+ },
+ $indexSet(receiver, key, value) {
+ throw A.wrapException(A.UnsupportedError$("Not supported"));
+ },
+ $isMap: 1
+ };
+ A.MidiInputMap_keys_closure.prototype = {
+ call$2(k, v) {
+ return B.JSArray_methods.add$1(this.keys, k);
+ },
+ $signature: 4
+ };
+ A.MidiOutputMap.prototype = {
+ containsKey$1(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(key)) != null;
+ },
+ $index(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(A._asString(key)));
+ },
+ forEach$1(receiver, f) {
+ var entries, entry, t1;
+ type$.void_Function_String_dynamic._as(f);
+ entries = receiver.entries();
+ for (; true;) {
+ entry = entries.next();
+ t1 = entry.done;
+ t1.toString;
+ if (t1)
+ return;
+ t1 = entry.value[0];
+ t1.toString;
+ f.call$2(t1, A.convertNativeToDart_Dictionary(entry.value[1]));
+ }
+ },
+ get$keys(receiver) {
+ var keys = A._setArrayType([], type$.JSArray_String);
+ this.forEach$1(receiver, new A.MidiOutputMap_keys_closure(keys));
+ return keys;
+ },
+ get$length(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1;
+ },
+ get$isEmpty(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1 === 0;
+ },
+ $indexSet(receiver, key, value) {
+ throw A.wrapException(A.UnsupportedError$("Not supported"));
+ },
+ $isMap: 1
+ };
+ A.MidiOutputMap_keys_closure.prototype = {
+ call$2(k, v) {
+ return B.JSArray_methods.add$1(this.keys, k);
+ },
+ $signature: 4
+ };
+ A.MimeType.prototype = {$isMimeType: 1};
+ A.MimeTypeArray.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.MimeType._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.Node.prototype = {
+ toString$0(receiver) {
+ var value = receiver.nodeValue;
+ return value == null ? this.super$Interceptor$toString(receiver) : value;
+ },
+ $isNode: 1
+ };
+ A.NodeList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Node._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.Plugin.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $isPlugin: 1
+ };
+ A.PluginArray.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Plugin._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.RtcStatsReport.prototype = {
+ containsKey$1(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(key)) != null;
+ },
+ $index(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(A._asString(key)));
+ },
+ forEach$1(receiver, f) {
+ var entries, entry, t1;
+ type$.void_Function_String_dynamic._as(f);
+ entries = receiver.entries();
+ for (; true;) {
+ entry = entries.next();
+ t1 = entry.done;
+ t1.toString;
+ if (t1)
+ return;
+ t1 = entry.value[0];
+ t1.toString;
+ f.call$2(t1, A.convertNativeToDart_Dictionary(entry.value[1]));
+ }
+ },
+ get$keys(receiver) {
+ var keys = A._setArrayType([], type$.JSArray_String);
+ this.forEach$1(receiver, new A.RtcStatsReport_keys_closure(keys));
+ return keys;
+ },
+ get$length(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1;
+ },
+ get$isEmpty(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1 === 0;
+ },
+ $indexSet(receiver, key, value) {
+ throw A.wrapException(A.UnsupportedError$("Not supported"));
+ },
+ $isMap: 1
+ };
+ A.RtcStatsReport_keys_closure.prototype = {
+ call$2(k, v) {
+ return B.JSArray_methods.add$1(this.keys, k);
+ },
+ $signature: 4
+ };
+ A.SelectElement.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.SourceBuffer.prototype = {$isSourceBuffer: 1};
+ A.SourceBufferList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.SourceBuffer._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.SpeechGrammar.prototype = {$isSpeechGrammar: 1};
+ A.SpeechGrammarList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.SpeechGrammar._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.SpeechRecognitionResult.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ },
+ $isSpeechRecognitionResult: 1
+ };
+ A.Storage.prototype = {
+ containsKey$1(receiver, key) {
+ return receiver.getItem(key) != null;
+ },
+ $index(receiver, key) {
+ return receiver.getItem(A._asString(key));
+ },
+ $indexSet(receiver, key, value) {
+ receiver.setItem(key, value);
+ },
+ forEach$1(receiver, f) {
+ var i, key, t1;
+ type$.void_Function_String_String._as(f);
+ for (i = 0; true; ++i) {
+ key = receiver.key(i);
+ if (key == null)
+ return;
+ t1 = receiver.getItem(key);
+ t1.toString;
+ f.call$2(key, t1);
+ }
+ },
+ get$keys(receiver) {
+ var keys = A._setArrayType([], type$.JSArray_String);
+ this.forEach$1(receiver, new A.Storage_keys_closure(keys));
+ return keys;
+ },
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ get$isEmpty(receiver) {
+ return receiver.key(0) == null;
+ },
+ $isMap: 1
+ };
+ A.Storage_keys_closure.prototype = {
+ call$2(k, v) {
+ return B.JSArray_methods.add$1(this.keys, k);
+ },
+ $signature: 27
+ };
+ A.StyleSheet.prototype = {$isStyleSheet: 1};
+ A.TextTrack.prototype = {$isTextTrack: 1};
+ A.TextTrackCue.prototype = {$isTextTrackCue: 1};
+ A.TextTrackCueList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.TextTrackCue._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.TextTrackList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.TextTrack._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.TimeRanges.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ }
+ };
+ A.Touch.prototype = {$isTouch: 1};
+ A.TouchList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Touch._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.TrackDefaultList.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.Url.prototype = {
+ toString$0(receiver) {
+ var t1 = String(receiver);
+ t1.toString;
+ return t1;
+ }
+ };
+ A.VideoTrackList.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A._CssRuleList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.CssRule._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A._DomRect.prototype = {
+ toString$0(receiver) {
+ var t2, t3, t4,
+ t1 = receiver.left;
+ t1.toString;
+ t2 = receiver.top;
+ t2.toString;
+ t3 = receiver.width;
+ t3.toString;
+ t4 = receiver.height;
+ t4.toString;
+ return "Rectangle (" + A.S(t1) + ", " + A.S(t2) + ") " + A.S(t3) + " x " + A.S(t4);
+ },
+ $eq(receiver, other) {
+ var t1, t2;
+ if (other == null)
+ return false;
+ if (type$.Rectangle_num._is(other)) {
+ t1 = receiver.left;
+ t1.toString;
+ t2 = other.left;
+ t2.toString;
+ if (t1 === t2) {
+ t1 = receiver.top;
+ t1.toString;
+ t2 = other.top;
+ t2.toString;
+ if (t1 === t2) {
+ t1 = receiver.width;
+ t1.toString;
+ t2 = J.getInterceptor$x(other);
+ if (t1 === t2.get$width(other)) {
+ t1 = receiver.height;
+ t1.toString;
+ t2 = t1 === t2.get$height(other);
+ t1 = t2;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ } else
+ t1 = false;
+ return t1;
+ },
+ get$hashCode(receiver) {
+ var t2, t3, t4,
+ t1 = receiver.left;
+ t1.toString;
+ t2 = receiver.top;
+ t2.toString;
+ t3 = receiver.width;
+ t3.toString;
+ t4 = receiver.height;
+ t4.toString;
+ return A.Object_hash(t1, t2, t3, t4);
+ },
+ get$_height(receiver) {
+ return receiver.height;
+ },
+ get$height(receiver) {
+ var t1 = receiver.height;
+ t1.toString;
+ return t1;
+ },
+ get$_width(receiver) {
+ return receiver.width;
+ },
+ get$width(receiver) {
+ var t1 = receiver.width;
+ t1.toString;
+ return t1;
+ }
+ };
+ A._GamepadList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ return receiver[index];
+ },
+ $indexSet(receiver, index, value) {
+ type$.nullable_Gamepad._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A._NamedNodeMap.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Node._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A._SpeechRecognitionResultList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.SpeechRecognitionResult._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A._StyleSheetList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length,
+ t2 = index >>> 0 !== index || index >= t1;
+ t2.toString;
+ if (t2)
+ throw A.wrapException(A.IndexError$withLength(index, t1, receiver, null));
+ t1 = receiver[index];
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.StyleSheet._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ if (!(index >= 0 && index < receiver.length))
+ return A.ioore(receiver, index);
+ return receiver[index];
+ },
+ $isEfficientLengthIterable: 1,
+ $isJavaScriptIndexingBehavior: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.ImmutableListMixin.prototype = {
+ get$iterator(receiver) {
+ return new A.FixedSizeListIterator(receiver, this.get$length(receiver), A.instanceType(receiver)._eval$1("FixedSizeListIterator<ImmutableListMixin.E>"));
+ }
+ };
+ A.FixedSizeListIterator.prototype = {
+ moveNext$0() {
+ var _this = this,
+ nextPosition = _this._position + 1,
+ t1 = _this._html$_length;
+ if (nextPosition < t1) {
+ _this.set$_html$_current(J.$index$asx(_this._array, nextPosition));
+ _this._position = nextPosition;
+ return true;
+ }
+ _this.set$_html$_current(null);
+ _this._position = t1;
+ return false;
+ },
+ get$current(_) {
+ var t1 = this._html$_current;
+ return t1 == null ? this.$ti._precomputed1._as(t1) : t1;
+ },
+ set$_html$_current(_current) {
+ this._html$_current = this.$ti._eval$1("1?")._as(_current);
+ },
+ $isIterator: 1
+ };
+ A._CssStyleDeclaration_JavaScriptObject_CssStyleDeclarationBase.prototype = {};
+ A._DomRectList_JavaScriptObject_ListMixin.prototype = {};
+ A._DomRectList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._DomStringList_JavaScriptObject_ListMixin.prototype = {};
+ A._DomStringList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._FileList_JavaScriptObject_ListMixin.prototype = {};
+ A._FileList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._HtmlCollection_JavaScriptObject_ListMixin.prototype = {};
+ A._HtmlCollection_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._MidiInputMap_JavaScriptObject_MapMixin.prototype = {};
+ A._MidiOutputMap_JavaScriptObject_MapMixin.prototype = {};
+ A._MimeTypeArray_JavaScriptObject_ListMixin.prototype = {};
+ A._MimeTypeArray_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._NodeList_JavaScriptObject_ListMixin.prototype = {};
+ A._NodeList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._PluginArray_JavaScriptObject_ListMixin.prototype = {};
+ A._PluginArray_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._RtcStatsReport_JavaScriptObject_MapMixin.prototype = {};
+ A._SourceBufferList_EventTarget_ListMixin.prototype = {};
+ A._SourceBufferList_EventTarget_ListMixin_ImmutableListMixin.prototype = {};
+ A._SpeechGrammarList_JavaScriptObject_ListMixin.prototype = {};
+ A._SpeechGrammarList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._Storage_JavaScriptObject_MapMixin.prototype = {};
+ A._TextTrackCueList_JavaScriptObject_ListMixin.prototype = {};
+ A._TextTrackCueList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._TextTrackList_EventTarget_ListMixin.prototype = {};
+ A._TextTrackList_EventTarget_ListMixin_ImmutableListMixin.prototype = {};
+ A._TouchList_JavaScriptObject_ListMixin.prototype = {};
+ A._TouchList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A.__CssRuleList_JavaScriptObject_ListMixin.prototype = {};
+ A.__CssRuleList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A.__GamepadList_JavaScriptObject_ListMixin.prototype = {};
+ A.__GamepadList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A.__NamedNodeMap_JavaScriptObject_ListMixin.prototype = {};
+ A.__NamedNodeMap_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin.prototype = {};
+ A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A.__StyleSheetList_JavaScriptObject_ListMixin.prototype = {};
+ A.__StyleSheetList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A.jsify__convert.prototype = {
+ call$1(o) {
+ var t1, convertedMap, t2, key, convertedList;
+ if (A._noJsifyRequired(o))
+ return o;
+ t1 = this._convertedObjects;
+ if (t1.containsKey$1(0, o))
+ return t1.$index(0, o);
+ if (type$.Map_of_nullable_Object_and_nullable_Object._is(o)) {
+ convertedMap = {};
+ t1.$indexSet(0, o, convertedMap);
+ for (t1 = J.getInterceptor$x(o), t2 = J.get$iterator$ax(t1.get$keys(o)); t2.moveNext$0();) {
+ key = t2.get$current(t2);
+ convertedMap[key] = this.call$1(t1.$index(o, key));
+ }
+ return convertedMap;
+ } else if (type$.Iterable_nullable_Object._is(o)) {
+ convertedList = [];
+ t1.$indexSet(0, o, convertedList);
+ B.JSArray_methods.addAll$1(convertedList, J.map$1$1$ax(o, this, type$.dynamic));
+ return convertedList;
+ } else
+ return o;
+ },
+ $signature: 19
+ };
+ A.promiseToFuture_closure.prototype = {
+ call$1(r) {
+ return this.completer.complete$1(0, this.T._eval$1("0/?")._as(r));
+ },
+ $signature: 5
+ };
+ A.promiseToFuture_closure0.prototype = {
+ call$1(e) {
+ if (e == null)
+ return this.completer.completeError$1(new A.NullRejectionException(e === undefined));
+ return this.completer.completeError$1(e);
+ },
+ $signature: 5
+ };
+ A.dartify_convert.prototype = {
+ call$1(o) {
+ var t1, millisSinceEpoch, proto, t2, dartObject, originalKeys, dartKeys, i, jsKey, dartKey, l, $length;
+ if (A._noDartifyRequired(o))
+ return o;
+ t1 = this._convertedObjects;
+ o.toString;
+ if (t1.containsKey$1(0, o))
+ return t1.$index(0, o);
+ if (o instanceof Date) {
+ millisSinceEpoch = o.getTime();
+ if (Math.abs(millisSinceEpoch) <= 864e13)
+ t1 = false;
+ else
+ t1 = true;
+ if (t1)
+ A.throwExpression(A.ArgumentError$("DateTime is outside valid range: " + millisSinceEpoch, null));
+ A.checkNotNullable(true, "isUtc", type$.bool);
+ return new A.DateTime(millisSinceEpoch, true);
+ }
+ if (o instanceof RegExp)
+ throw A.wrapException(A.ArgumentError$("structured clone of RegExp", null));
+ if (typeof Promise != "undefined" && o instanceof Promise)
+ return A.promiseToFuture(o, type$.nullable_Object);
+ proto = Object.getPrototypeOf(o);
+ if (proto === Object.prototype || proto === null) {
+ t2 = type$.nullable_Object;
+ dartObject = A.LinkedHashMap_LinkedHashMap$_empty(t2, t2);
+ t1.$indexSet(0, o, dartObject);
+ originalKeys = Object.keys(o);
+ dartKeys = [];
+ for (t1 = J.getInterceptor$ax(originalKeys), t2 = t1.get$iterator(originalKeys); t2.moveNext$0();)
+ dartKeys.push(A.dartify(t2.get$current(t2)));
+ for (i = 0; i < t1.get$length(originalKeys); ++i) {
+ jsKey = t1.$index(originalKeys, i);
+ if (!(i < dartKeys.length))
+ return A.ioore(dartKeys, i);
+ dartKey = dartKeys[i];
+ if (jsKey != null)
+ dartObject.$indexSet(0, dartKey, this.call$1(o[jsKey]));
+ }
+ return dartObject;
+ }
+ if (o instanceof Array) {
+ l = o;
+ dartObject = [];
+ t1.$indexSet(0, o, dartObject);
+ $length = A._asInt(o.length);
+ for (t1 = J.getInterceptor$asx(l), i = 0; i < $length; ++i)
+ dartObject.push(this.call$1(t1.$index(l, i)));
+ return dartObject;
+ }
+ return o;
+ },
+ $signature: 19
+ };
+ A.NullRejectionException.prototype = {
+ toString$0(_) {
+ return "Promise was rejected with a value of `" + (this.isUndefined ? "undefined" : "null") + "`.";
+ },
+ $isException: 1
+ };
+ A.Length.prototype = {$isLength: 1};
+ A.LengthList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length;
+ t1.toString;
+ t1 = index >>> 0 !== index || index >= t1;
+ t1.toString;
+ if (t1)
+ throw A.wrapException(A.IndexError$withLength(index, this.get$length(receiver), receiver, null));
+ t1 = receiver.getItem(index);
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Length._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ return this.$index(receiver, index);
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.Number.prototype = {$isNumber: 1};
+ A.NumberList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length;
+ t1.toString;
+ t1 = index >>> 0 !== index || index >= t1;
+ t1.toString;
+ if (t1)
+ throw A.wrapException(A.IndexError$withLength(index, this.get$length(receiver), receiver, null));
+ t1 = receiver.getItem(index);
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Number._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ return this.$index(receiver, index);
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.PointList.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.StringList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length;
+ t1.toString;
+ t1 = index >>> 0 !== index || index >= t1;
+ t1.toString;
+ if (t1)
+ throw A.wrapException(A.IndexError$withLength(index, this.get$length(receiver), receiver, null));
+ t1 = receiver.getItem(index);
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ A._asString(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ return this.$index(receiver, index);
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A.Transform.prototype = {$isTransform: 1};
+ A.TransformList.prototype = {
+ get$length(receiver) {
+ var t1 = receiver.length;
+ t1.toString;
+ return t1;
+ },
+ $index(receiver, index) {
+ var t1 = receiver.length;
+ t1.toString;
+ t1 = index >>> 0 !== index || index >= t1;
+ t1.toString;
+ if (t1)
+ throw A.wrapException(A.IndexError$withLength(index, this.get$length(receiver), receiver, null));
+ t1 = receiver.getItem(index);
+ t1.toString;
+ return t1;
+ },
+ $indexSet(receiver, index, value) {
+ type$.Transform._as(value);
+ throw A.wrapException(A.UnsupportedError$("Cannot assign element of immutable List."));
+ },
+ elementAt$1(receiver, index) {
+ return this.$index(receiver, index);
+ },
+ $isEfficientLengthIterable: 1,
+ $isIterable: 1,
+ $isList: 1
+ };
+ A._LengthList_JavaScriptObject_ListMixin.prototype = {};
+ A._LengthList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._NumberList_JavaScriptObject_ListMixin.prototype = {};
+ A._NumberList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._StringList_JavaScriptObject_ListMixin.prototype = {};
+ A._StringList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A._TransformList_JavaScriptObject_ListMixin.prototype = {};
+ A._TransformList_JavaScriptObject_ListMixin_ImmutableListMixin.prototype = {};
+ A.AudioBuffer.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.AudioParamMap.prototype = {
+ containsKey$1(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(key)) != null;
+ },
+ $index(receiver, key) {
+ return A.convertNativeToDart_Dictionary(receiver.get(A._asString(key)));
+ },
+ forEach$1(receiver, f) {
+ var entries, entry, t1;
+ type$.void_Function_String_dynamic._as(f);
+ entries = receiver.entries();
+ for (; true;) {
+ entry = entries.next();
+ t1 = entry.done;
+ t1.toString;
+ if (t1)
+ return;
+ t1 = entry.value[0];
+ t1.toString;
+ f.call$2(t1, A.convertNativeToDart_Dictionary(entry.value[1]));
+ }
+ },
+ get$keys(receiver) {
+ var keys = A._setArrayType([], type$.JSArray_String);
+ this.forEach$1(receiver, new A.AudioParamMap_keys_closure(keys));
+ return keys;
+ },
+ get$length(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1;
+ },
+ get$isEmpty(receiver) {
+ var t1 = receiver.size;
+ t1.toString;
+ return t1 === 0;
+ },
+ $indexSet(receiver, key, value) {
+ throw A.wrapException(A.UnsupportedError$("Not supported"));
+ },
+ $isMap: 1
+ };
+ A.AudioParamMap_keys_closure.prototype = {
+ call$2(k, v) {
+ return B.JSArray_methods.add$1(this.keys, k);
+ },
+ $signature: 4
+ };
+ A.AudioTrackList.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A.BaseAudioContext.prototype = {};
+ A.OfflineAudioContext.prototype = {
+ get$length(receiver) {
+ return receiver.length;
+ }
+ };
+ A._AudioParamMap_JavaScriptObject_MapMixin.prototype = {};
+ A.NullStreamSink.prototype = {
+ addStream$1(_, stream) {
+ var _this = this;
+ _this.$ti._eval$1("Stream<1>")._as(stream);
+ _this._checkEventAllowed$0();
+ _this._addingStream = true;
+ return stream.listen$1(null).cancel$0(0).whenComplete$1(new A.NullStreamSink_addStream_closure(_this));
+ },
+ _checkEventAllowed$0() {
+ if (this._null_stream_sink$_closed)
+ throw A.wrapException(A.StateError$("Cannot add to a closed sink."));
+ if (this._addingStream)
+ throw A.wrapException(A.StateError$("Cannot add to a sink while adding a stream."));
+ },
+ close$0(_) {
+ this._null_stream_sink$_closed = true;
+ return this.done;
+ },
+ $isStreamConsumer: 1,
+ $isStreamSink: 1
+ };
+ A.NullStreamSink_addStream_closure.prototype = {
+ call$0() {
+ this.$this._addingStream = false;
+ },
+ $signature: 3
+ };
+ A.Context.prototype = {
+ absolute$15(_, part1, part2, part3, part4, part5, part6, part7, part8, part9, part10, part11, part12, part13, part14, part15) {
+ var t1;
+ A._validateArgList("absolute", A._setArrayType([part1, part2, part3, part4, part5, part6, part7, part8, part9, part10, part11, part12, part13, part14, part15], type$.JSArray_nullable_String));
+ t1 = this.style;
+ t1 = t1.rootLength$1(part1) > 0 && !t1.isRootRelative$1(part1);
+ if (t1)
+ return part1;
+ t1 = this._context$_current;
+ return this.join$16(0, t1 == null ? A.current() : t1, part1, part2, part3, part4, part5, part6, part7, part8, part9, part10, part11, part12, part13, part14, part15);
+ },
+ absolute$1($receiver, part1) {
+ return this.absolute$15($receiver, part1, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
+ },
+ join$16(_, part1, part2, part3, part4, part5, part6, part7, part8, part9, part10, part11, part12, part13, part14, part15, part16) {
+ var parts = A._setArrayType([part1, part2, part3, part4, part5, part6, part7, part8, part9, part10, part11, part12, part13, part14, part15, part16], type$.JSArray_nullable_String);
+ A._validateArgList("join", parts);
+ return this.joinAll$1(new A.WhereTypeIterable(parts, type$.WhereTypeIterable_String));
+ },
+ join$2($receiver, part1, part2) {
+ return this.join$16($receiver, part1, part2, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
+ },
+ joinAll$1(parts) {
+ var t1, t2, t3, needsSeparator, isAbsoluteAndNotRootRelative, t4, t5, parsed, path, t6;
+ type$.Iterable_String._as(parts);
+ for (t1 = parts.$ti, t2 = t1._eval$1("bool(Iterable.E)")._as(new A.Context_joinAll_closure()), t3 = parts.get$iterator(parts), t1 = new A.WhereIterator(t3, t2, t1._eval$1("WhereIterator<Iterable.E>")), t2 = this.style, needsSeparator = false, isAbsoluteAndNotRootRelative = false, t4 = ""; t1.moveNext$0();) {
+ t5 = t3.get$current(t3);
+ if (t2.isRootRelative$1(t5) && isAbsoluteAndNotRootRelative) {
+ parsed = A.ParsedPath_ParsedPath$parse(t5, t2);
+ path = t4.charCodeAt(0) == 0 ? t4 : t4;
+ t4 = B.JSString_methods.substring$2(path, 0, t2.rootLength$2$withDrive(path, true));
+ parsed.root = t4;
+ if (t2.needsSeparator$1(t4))
+ B.JSArray_methods.$indexSet(parsed.separators, 0, t2.get$separator());
+ t4 = "" + parsed.toString$0(0);
+ } else if (t2.rootLength$1(t5) > 0) {
+ isAbsoluteAndNotRootRelative = !t2.isRootRelative$1(t5);
+ t4 = "" + t5;
+ } else {
+ t6 = t5.length;
+ if (t6 !== 0) {
+ if (0 >= t6)
+ return A.ioore(t5, 0);
+ t6 = t2.containsSeparator$1(t5[0]);
+ } else
+ t6 = false;
+ if (!t6)
+ if (needsSeparator)
+ t4 += t2.get$separator();
+ t4 += t5;
+ }
+ needsSeparator = t2.needsSeparator$1(t5);
+ }
+ return t4.charCodeAt(0) == 0 ? t4 : t4;
+ },
+ split$1(_, path) {
+ var parsed = A.ParsedPath_ParsedPath$parse(path, this.style),
+ t1 = parsed.parts,
+ t2 = A._arrayInstanceType(t1),
+ t3 = t2._eval$1("WhereIterable<1>");
+ parsed.set$parts(A.List_List$of(new A.WhereIterable(t1, t2._eval$1("bool(1)")._as(new A.Context_split_closure()), t3), true, t3._eval$1("Iterable.E")));
+ t1 = parsed.root;
+ if (t1 != null)
+ B.JSArray_methods.insert$2(parsed.parts, 0, t1);
+ return parsed.parts;
+ },
+ normalize$1(_, path) {
+ var parsed;
+ if (!this._needsNormalization$1(path))
+ return path;
+ parsed = A.ParsedPath_ParsedPath$parse(path, this.style);
+ parsed.normalize$0(0);
+ return parsed.toString$0(0);
+ },
+ _needsNormalization$1(path) {
+ var t2, i, start, previous, t3, previousPrevious, codeUnit, t4,
+ t1 = this.style,
+ root = t1.rootLength$1(path);
+ if (root !== 0) {
+ if (t1 === $.$get$Style_windows())
+ for (t2 = path.length, i = 0; i < root; ++i) {
+ if (!(i < t2))
+ return A.ioore(path, i);
+ if (path.charCodeAt(i) === 47)
+ return true;
+ }
+ start = root;
+ previous = 47;
+ } else {
+ start = 0;
+ previous = null;
+ }
+ for (t2 = new A.CodeUnits(path)._string, t3 = t2.length, i = start, previousPrevious = null; i < t3; ++i, previousPrevious = previous, previous = codeUnit) {
+ if (!(i >= 0))
+ return A.ioore(t2, i);
+ codeUnit = t2.charCodeAt(i);
+ if (t1.isSeparator$1(codeUnit)) {
+ if (t1 === $.$get$Style_windows() && codeUnit === 47)
+ return true;
+ if (previous != null && t1.isSeparator$1(previous))
+ return true;
+ if (previous === 46)
+ t4 = previousPrevious == null || previousPrevious === 46 || t1.isSeparator$1(previousPrevious);
+ else
+ t4 = false;
+ if (t4)
+ return true;
+ }
+ }
+ if (previous == null)
+ return true;
+ if (t1.isSeparator$1(previous))
+ return true;
+ if (previous === 46)
+ t1 = previousPrevious == null || t1.isSeparator$1(previousPrevious) || previousPrevious === 46;
+ else
+ t1 = false;
+ if (t1)
+ return true;
+ return false;
+ },
+ relative$1(path) {
+ var from, fromParsed, pathParsed, t3, t4, t5, _this = this,
+ _s26_ = 'Unable to find a path to "',
+ t1 = _this.style,
+ t2 = t1.rootLength$1(path);
+ if (t2 <= 0)
+ return _this.normalize$1(0, path);
+ t2 = _this._context$_current;
+ from = t2 == null ? A.current() : t2;
+ if (t1.rootLength$1(from) <= 0 && t1.rootLength$1(path) > 0)
+ return _this.normalize$1(0, path);
+ if (t1.rootLength$1(path) <= 0 || t1.isRootRelative$1(path))
+ path = _this.absolute$1(0, path);
+ if (t1.rootLength$1(path) <= 0 && t1.rootLength$1(from) > 0)
+ throw A.wrapException(A.PathException$(_s26_ + path + '" from "' + from + '".'));
+ fromParsed = A.ParsedPath_ParsedPath$parse(from, t1);
+ fromParsed.normalize$0(0);
+ pathParsed = A.ParsedPath_ParsedPath$parse(path, t1);
+ pathParsed.normalize$0(0);
+ t2 = fromParsed.parts;
+ t3 = t2.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return A.ioore(t2, 0);
+ t2 = J.$eq$(t2[0], ".");
+ } else
+ t2 = false;
+ if (t2)
+ return pathParsed.toString$0(0);
+ t2 = fromParsed.root;
+ t3 = pathParsed.root;
+ if (t2 != t3)
+ t2 = t2 == null || t3 == null || !t1.pathsEqual$2(t2, t3);
+ else
+ t2 = false;
+ if (t2)
+ return pathParsed.toString$0(0);
+ while (true) {
+ t2 = fromParsed.parts;
+ t3 = t2.length;
+ if (t3 !== 0) {
+ t4 = pathParsed.parts;
+ t5 = t4.length;
+ if (t5 !== 0) {
+ if (0 >= t3)
+ return A.ioore(t2, 0);
+ t2 = t2[0];
+ if (0 >= t5)
+ return A.ioore(t4, 0);
+ t4 = t1.pathsEqual$2(t2, t4[0]);
+ t2 = t4;
+ } else
+ t2 = false;
+ } else
+ t2 = false;
+ if (!t2)
+ break;
+ B.JSArray_methods.removeAt$1(fromParsed.parts, 0);
+ B.JSArray_methods.removeAt$1(fromParsed.separators, 1);
+ B.JSArray_methods.removeAt$1(pathParsed.parts, 0);
+ B.JSArray_methods.removeAt$1(pathParsed.separators, 1);
+ }
+ t2 = fromParsed.parts;
+ t3 = t2.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return A.ioore(t2, 0);
+ t2 = J.$eq$(t2[0], "..");
+ } else
+ t2 = false;
+ if (t2)
+ throw A.wrapException(A.PathException$(_s26_ + path + '" from "' + from + '".'));
+ t2 = type$.String;
+ B.JSArray_methods.insertAll$2(pathParsed.parts, 0, A.List_List$filled(fromParsed.parts.length, "..", false, t2));
+ B.JSArray_methods.$indexSet(pathParsed.separators, 0, "");
+ B.JSArray_methods.insertAll$2(pathParsed.separators, 1, A.List_List$filled(fromParsed.parts.length, t1.get$separator(), false, t2));
+ t1 = pathParsed.parts;
+ t2 = t1.length;
+ if (t2 === 0)
+ return ".";
+ if (t2 > 1 && J.$eq$(B.JSArray_methods.get$last(t1), ".")) {
+ B.JSArray_methods.removeLast$0(pathParsed.parts);
+ t1 = pathParsed.separators;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ B.JSArray_methods.add$1(t1, "");
+ }
+ pathParsed.root = "";
+ pathParsed.removeTrailingSeparators$0();
+ return pathParsed.toString$0(0);
+ },
+ toUri$1(path) {
+ var t2,
+ t1 = this.style;
+ if (t1.rootLength$1(path) <= 0)
+ return t1.relativePathToUri$1(path);
+ else {
+ t2 = this._context$_current;
+ return t1.absolutePathToUri$1(this.join$2(0, t2 == null ? A.current() : t2, path));
+ }
+ },
+ prettyUri$1(uri) {
+ var path, rel, _this = this,
+ typedUri = A._parseUri(uri);
+ if (typedUri.get$scheme() === "file" && _this.style === $.$get$Style_url())
+ return typedUri.toString$0(0);
+ else if (typedUri.get$scheme() !== "file" && typedUri.get$scheme() !== "" && _this.style !== $.$get$Style_url())
+ return typedUri.toString$0(0);
+ path = _this.normalize$1(0, _this.style.pathFromUri$1(A._parseUri(typedUri)));
+ rel = _this.relative$1(path);
+ return _this.split$1(0, rel).length > _this.split$1(0, path).length ? path : rel;
+ }
+ };
+ A.Context_joinAll_closure.prototype = {
+ call$1(part) {
+ return A._asString(part) !== "";
+ },
+ $signature: 1
+ };
+ A.Context_split_closure.prototype = {
+ call$1(part) {
+ return A._asString(part).length !== 0;
+ },
+ $signature: 1
+ };
+ A._validateArgList_closure.prototype = {
+ call$1(arg) {
+ A._asStringQ(arg);
+ return arg == null ? "null" : '"' + arg + '"';
+ },
+ $signature: 31
+ };
+ A.InternalStyle.prototype = {
+ getRoot$1(path) {
+ var t1,
+ $length = this.rootLength$1(path);
+ if ($length > 0)
+ return B.JSString_methods.substring$2(path, 0, $length);
+ if (this.isRootRelative$1(path)) {
+ if (0 >= path.length)
+ return A.ioore(path, 0);
+ t1 = path[0];
+ } else
+ t1 = null;
+ return t1;
+ },
+ relativePathToUri$1(path) {
+ var segments, t2, _null = null,
+ t1 = path.length;
+ if (t1 === 0)
+ return A._Uri__Uri(_null, _null, _null, _null);
+ segments = A.Context_Context(this).split$1(0, path);
+ t2 = t1 - 1;
+ if (!(t2 >= 0))
+ return A.ioore(path, t2);
+ if (this.isSeparator$1(path.charCodeAt(t2)))
+ B.JSArray_methods.add$1(segments, "");
+ return A._Uri__Uri(_null, _null, segments, _null);
+ },
+ pathsEqual$2(path1, path2) {
+ return path1 === path2;
+ }
+ };
+ A.ParsedPath.prototype = {
+ get$hasTrailingSeparator() {
+ var t1 = this.parts;
+ if (t1.length !== 0)
+ t1 = J.$eq$(B.JSArray_methods.get$last(t1), "") || !J.$eq$(B.JSArray_methods.get$last(this.separators), "");
+ else
+ t1 = false;
+ return t1;
+ },
+ removeTrailingSeparators$0() {
+ var t1, t2, _this = this;
+ while (true) {
+ t1 = _this.parts;
+ if (!(t1.length !== 0 && J.$eq$(B.JSArray_methods.get$last(t1), "")))
+ break;
+ B.JSArray_methods.removeLast$0(_this.parts);
+ t1 = _this.separators;
+ if (0 >= t1.length)
+ return A.ioore(t1, -1);
+ t1.pop();
+ }
+ t1 = _this.separators;
+ t2 = t1.length;
+ if (t2 !== 0)
+ B.JSArray_methods.$indexSet(t1, t2 - 1, "");
+ },
+ normalize$0(_) {
+ var t1, t2, leadingDoubles, _i, part, t3, _this = this,
+ newParts = A._setArrayType([], type$.JSArray_String);
+ for (t1 = _this.parts, t2 = t1.length, leadingDoubles = 0, _i = 0; _i < t1.length; t1.length === t2 || (0, A.throwConcurrentModificationError)(t1), ++_i) {
+ part = t1[_i];
+ t3 = J.getInterceptor$(part);
+ if (!(t3.$eq(part, ".") || t3.$eq(part, "")))
+ if (t3.$eq(part, "..")) {
+ t3 = newParts.length;
+ if (t3 !== 0) {
+ if (0 >= t3)
+ return A.ioore(newParts, -1);
+ newParts.pop();
+ } else
+ ++leadingDoubles;
+ } else
+ B.JSArray_methods.add$1(newParts, part);
+ }
+ if (_this.root == null)
+ B.JSArray_methods.insertAll$2(newParts, 0, A.List_List$filled(leadingDoubles, "..", false, type$.String));
+ if (newParts.length === 0 && _this.root == null)
+ B.JSArray_methods.add$1(newParts, ".");
+ _this.set$parts(newParts);
+ t1 = _this.style;
+ _this.set$separators(A.List_List$filled(newParts.length + 1, t1.get$separator(), true, type$.String));
+ t2 = _this.root;
+ if (t2 == null || newParts.length === 0 || !t1.needsSeparator$1(t2))
+ B.JSArray_methods.$indexSet(_this.separators, 0, "");
+ t2 = _this.root;
+ if (t2 != null && t1 === $.$get$Style_windows()) {
+ t2.toString;
+ _this.root = A.stringReplaceAllUnchecked(t2, "/", "\\");
+ }
+ _this.removeTrailingSeparators$0();
+ },
+ toString$0(_) {
+ var i, t2, t3, _this = this,
+ t1 = _this.root;
+ t1 = t1 != null ? "" + t1 : "";
+ for (i = 0; i < _this.parts.length; ++i, t1 = t3) {
+ t2 = _this.separators;
+ if (!(i < t2.length))
+ return A.ioore(t2, i);
+ t2 = A.S(t2[i]);
+ t3 = _this.parts;
+ if (!(i < t3.length))
+ return A.ioore(t3, i);
+ t3 = t1 + t2 + A.S(t3[i]);
+ }
+ t1 += A.S(B.JSArray_methods.get$last(_this.separators));
+ return t1.charCodeAt(0) == 0 ? t1 : t1;
+ },
+ set$parts(parts) {
+ this.parts = type$.List_String._as(parts);
+ },
+ set$separators(separators) {
+ this.separators = type$.List_String._as(separators);
+ }
+ };
+ A.PathException.prototype = {
+ toString$0(_) {
+ return "PathException: " + this.message;
+ },
+ $isException: 1
+ };
+ A.Style.prototype = {
+ toString$0(_) {
+ return this.get$name(this);
+ }
+ };
+ A.PosixStyle.prototype = {
+ containsSeparator$1(path) {
+ return B.JSString_methods.contains$1(path, "/");
+ },
+ isSeparator$1(codeUnit) {
+ return codeUnit === 47;
+ },
+ needsSeparator$1(path) {
+ var t2,
+ t1 = path.length;
+ if (t1 !== 0) {
+ t2 = t1 - 1;
+ if (!(t2 >= 0))
+ return A.ioore(path, t2);
+ t2 = path.charCodeAt(t2) !== 47;
+ t1 = t2;
+ } else
+ t1 = false;
+ return t1;
+ },
+ rootLength$2$withDrive(path, withDrive) {
+ var t1 = path.length;
+ if (t1 !== 0) {
+ if (0 >= t1)
+ return A.ioore(path, 0);
+ t1 = path.charCodeAt(0) === 47;
+ } else
+ t1 = false;
+ if (t1)
+ return 1;
+ return 0;
+ },
+ rootLength$1(path) {
+ return this.rootLength$2$withDrive(path, false);
+ },
+ isRootRelative$1(path) {
+ return false;
+ },
+ pathFromUri$1(uri) {
+ var t1;
+ if (uri.get$scheme() === "" || uri.get$scheme() === "file") {
+ t1 = uri.get$path(uri);
+ return A._Uri__uriDecode(t1, 0, t1.length, B.C_Utf8Codec, false);
+ }
+ throw A.wrapException(A.ArgumentError$("Uri " + uri.toString$0(0) + " must have scheme 'file:'.", null));
+ },
+ absolutePathToUri$1(path) {
+ var parsed = A.ParsedPath_ParsedPath$parse(path, this),
+ t1 = parsed.parts;
+ if (t1.length === 0)
+ B.JSArray_methods.addAll$1(t1, A._setArrayType(["", ""], type$.JSArray_String));
+ else if (parsed.get$hasTrailingSeparator())
+ B.JSArray_methods.add$1(parsed.parts, "");
+ return A._Uri__Uri(null, null, parsed.parts, "file");
+ },
+ get$name() {
+ return "posix";
+ },
+ get$separator() {
+ return "/";
+ }
+ };
+ A.UrlStyle.prototype = {
+ containsSeparator$1(path) {
+ return B.JSString_methods.contains$1(path, "/");
+ },
+ isSeparator$1(codeUnit) {
+ return codeUnit === 47;
+ },
+ needsSeparator$1(path) {
+ var t2,
+ t1 = path.length;
+ if (t1 === 0)
+ return false;
+ t2 = t1 - 1;
+ if (!(t2 >= 0))
+ return A.ioore(path, t2);
+ if (path.charCodeAt(t2) !== 47)
+ return true;
+ return B.JSString_methods.endsWith$1(path, "://") && this.rootLength$1(path) === t1;
+ },
+ rootLength$2$withDrive(path, withDrive) {
+ var i, codeUnit, index, t2,
+ t1 = path.length;
+ if (t1 === 0)
+ return 0;
+ if (0 >= t1)
+ return A.ioore(path, 0);
+ if (path.charCodeAt(0) === 47)
+ return 1;
+ for (i = 0; i < t1; ++i) {
+ codeUnit = path.charCodeAt(i);
+ if (codeUnit === 47)
+ return 0;
+ if (codeUnit === 58) {
+ if (i === 0)
+ return 0;
+ index = B.JSString_methods.indexOf$2(path, "/", B.JSString_methods.startsWith$2(path, "//", i + 1) ? i + 3 : i);
+ if (index <= 0)
+ return t1;
+ if (!withDrive || t1 < index + 3)
+ return index;
+ if (!B.JSString_methods.startsWith$1(path, "file://"))
+ return index;
+ if (!A.isDriveLetter(path, index + 1))
+ return index;
+ t2 = index + 3;
+ return t1 === t2 ? t2 : index + 4;
+ }
+ }
+ return 0;
+ },
+ rootLength$1(path) {
+ return this.rootLength$2$withDrive(path, false);
+ },
+ isRootRelative$1(path) {
+ var t1 = path.length;
+ if (t1 !== 0) {
+ if (0 >= t1)
+ return A.ioore(path, 0);
+ t1 = path.charCodeAt(0) === 47;
+ } else
+ t1 = false;
+ return t1;
+ },
+ pathFromUri$1(uri) {
+ return uri.toString$0(0);
+ },
+ relativePathToUri$1(path) {
+ return A.Uri_parse(path);
+ },
+ absolutePathToUri$1(path) {
+ return A.Uri_parse(path);
+ },
+ get$name() {
+ return "url";
+ },
+ get$separator() {
+ return "/";
+ }
+ };
+ A.WindowsStyle.prototype = {
+ containsSeparator$1(path) {
+ return B.JSString_methods.contains$1(path, "/");
+ },
+ isSeparator$1(codeUnit) {
+ return codeUnit === 47 || codeUnit === 92;
+ },
+ needsSeparator$1(path) {
+ var t2,
+ t1 = path.length;
+ if (t1 === 0)
+ return false;
+ t2 = t1 - 1;
+ if (!(t2 >= 0))
+ return A.ioore(path, t2);
+ t2 = path.charCodeAt(t2);
+ return !(t2 === 47 || t2 === 92);
+ },
+ rootLength$2$withDrive(path, withDrive) {
+ var t2, index,
+ t1 = path.length;
+ if (t1 === 0)
+ return 0;
+ if (0 >= t1)
+ return A.ioore(path, 0);
+ if (path.charCodeAt(0) === 47)
+ return 1;
+ if (path.charCodeAt(0) === 92) {
+ if (t1 >= 2) {
+ if (1 >= t1)
+ return A.ioore(path, 1);
+ t2 = path.charCodeAt(1) !== 92;
+ } else
+ t2 = true;
+ if (t2)
+ return 1;
+ index = B.JSString_methods.indexOf$2(path, "\\", 2);
+ if (index > 0) {
+ index = B.JSString_methods.indexOf$2(path, "\\", index + 1);
+ if (index > 0)
+ return index;
+ }
+ return t1;
+ }
+ if (t1 < 3)
+ return 0;
+ if (!A.isAlphabetic(path.charCodeAt(0)))
+ return 0;
+ if (path.charCodeAt(1) !== 58)
+ return 0;
+ t1 = path.charCodeAt(2);
+ if (!(t1 === 47 || t1 === 92))
+ return 0;
+ return 3;
+ },
+ rootLength$1(path) {
+ return this.rootLength$2$withDrive(path, false);
+ },
+ isRootRelative$1(path) {
+ return this.rootLength$1(path) === 1;
+ },
+ pathFromUri$1(uri) {
+ var path, t1;
+ if (uri.get$scheme() !== "" && uri.get$scheme() !== "file")
+ throw A.wrapException(A.ArgumentError$("Uri " + uri.toString$0(0) + " must have scheme 'file:'.", null));
+ path = uri.get$path(uri);
+ if (uri.get$host(uri) === "") {
+ if (path.length >= 3 && B.JSString_methods.startsWith$1(path, "/") && A.isDriveLetter(path, 1))
+ path = B.JSString_methods.replaceFirst$2(path, "/", "");
+ } else
+ path = "\\\\" + uri.get$host(uri) + path;
+ t1 = A.stringReplaceAllUnchecked(path, "/", "\\");
+ return A._Uri__uriDecode(t1, 0, t1.length, B.C_Utf8Codec, false);
+ },
+ absolutePathToUri$1(path) {
+ var rootParts, t2,
+ parsed = A.ParsedPath_ParsedPath$parse(path, this),
+ t1 = parsed.root;
+ t1.toString;
+ if (B.JSString_methods.startsWith$1(t1, "\\\\")) {
+ rootParts = new A.WhereIterable(A._setArrayType(t1.split("\\"), type$.JSArray_String), type$.bool_Function_String._as(new A.WindowsStyle_absolutePathToUri_closure()), type$.WhereIterable_String);
+ B.JSArray_methods.insert$2(parsed.parts, 0, rootParts.get$last(rootParts));
+ if (parsed.get$hasTrailingSeparator())
+ B.JSArray_methods.add$1(parsed.parts, "");
+ return A._Uri__Uri(rootParts.get$first(rootParts), null, parsed.parts, "file");
+ } else {
+ if (parsed.parts.length === 0 || parsed.get$hasTrailingSeparator())
+ B.JSArray_methods.add$1(parsed.parts, "");
+ t1 = parsed.parts;
+ t2 = parsed.root;
+ t2.toString;
+ t2 = A.stringReplaceAllUnchecked(t2, "/", "");
+ B.JSArray_methods.insert$2(t1, 0, A.stringReplaceAllUnchecked(t2, "\\", ""));
+ return A._Uri__Uri(null, null, parsed.parts, "file");
+ }
+ },
+ codeUnitsEqual$2(codeUnit1, codeUnit2) {
+ var upperCase1;
+ if (codeUnit1 === codeUnit2)
+ return true;
+ if (codeUnit1 === 47)
+ return codeUnit2 === 92;
+ if (codeUnit1 === 92)
+ return codeUnit2 === 47;
+ if ((codeUnit1 ^ codeUnit2) !== 32)
+ return false;
+ upperCase1 = codeUnit1 | 32;
+ return upperCase1 >= 97 && upperCase1 <= 122;
+ },
+ pathsEqual$2(path1, path2) {
+ var t1, t2, i;
+ if (path1 === path2)
+ return true;
+ t1 = path1.length;
+ t2 = path2.length;
+ if (t1 !== t2)
+ return false;
+ for (i = 0; i < t1; ++i) {
+ if (!(i < t2))
+ return A.ioore(path2, i);
+ if (!this.codeUnitsEqual$2(path1.charCodeAt(i), path2.charCodeAt(i)))
+ return false;
+ }
+ return true;
+ },
+ get$name() {
+ return "windows";
+ },
+ get$separator() {
+ return "\\";
+ }
+ };
+ A.WindowsStyle_absolutePathToUri_closure.prototype = {
+ call$1(part) {
+ return A._asString(part) !== "";
+ },
+ $signature: 1
+ };
+ A.Chain.prototype = {
+ toTrace$0() {
+ var t1 = this.traces,
+ t2 = A._arrayInstanceType(t1);
+ return A.Trace$(new A.ExpandIterable(t1, t2._eval$1("Iterable<Frame>(1)")._as(new A.Chain_toTrace_closure()), t2._eval$1("ExpandIterable<1,Frame>")), null);
+ },
+ toString$0(_) {
+ var t1 = this.traces,
+ t2 = A._arrayInstanceType(t1);
+ return new A.MappedListIterable(t1, t2._eval$1("String(1)")._as(new A.Chain_toString_closure(new A.MappedListIterable(t1, t2._eval$1("int(1)")._as(new A.Chain_toString_closure0()), t2._eval$1("MappedListIterable<1,int>")).fold$1$2(0, 0, B.CONSTANT, type$.int))), t2._eval$1("MappedListIterable<1,String>")).join$1(0, string$.x3d_____);
+ },
+ $isStackTrace: 1
+ };
+ A.Chain_Chain$parse_closure.prototype = {
+ call$1(line) {
+ return A._asString(line).length !== 0;
+ },
+ $signature: 1
+ };
+ A.Chain_toTrace_closure.prototype = {
+ call$1(trace) {
+ return type$.Trace._as(trace).get$frames();
+ },
+ $signature: 32
+ };
+ A.Chain_toString_closure0.prototype = {
+ call$1(trace) {
+ var t1 = type$.Trace._as(trace).get$frames(),
+ t2 = A._arrayInstanceType(t1);
+ return new A.MappedListIterable(t1, t2._eval$1("int(1)")._as(new A.Chain_toString__closure0()), t2._eval$1("MappedListIterable<1,int>")).fold$1$2(0, 0, B.CONSTANT, type$.int);
+ },
+ $signature: 33
+ };
+ A.Chain_toString__closure0.prototype = {
+ call$1(frame) {
+ type$.Frame._as(frame);
+ return frame.get$location(frame).length;
+ },
+ $signature: 20
+ };
+ A.Chain_toString_closure.prototype = {
+ call$1(trace) {
+ var t1 = type$.Trace._as(trace).get$frames(),
+ t2 = A._arrayInstanceType(t1);
+ return new A.MappedListIterable(t1, t2._eval$1("String(1)")._as(new A.Chain_toString__closure(this.longest)), t2._eval$1("MappedListIterable<1,String>")).join$0(0);
+ },
+ $signature: 35
+ };
+ A.Chain_toString__closure.prototype = {
+ call$1(frame) {
+ type$.Frame._as(frame);
+ return B.JSString_methods.padRight$1(frame.get$location(frame), this.longest) + " " + A.S(frame.get$member()) + "\n";
+ },
+ $signature: 21
+ };
+ A.Frame.prototype = {
+ get$isCore() {
+ return this.uri.get$scheme() === "dart";
+ },
+ get$library() {
+ var t1 = this.uri;
+ if (t1.get$scheme() === "data")
+ return "data:...";
+ return $.$get$context().prettyUri$1(t1);
+ },
+ get$$package() {
+ var t1 = this.uri;
+ if (t1.get$scheme() !== "package")
+ return null;
+ return B.JSArray_methods.get$first(t1.get$path(t1).split("/"));
+ },
+ get$location(_) {
+ var t2, _this = this,
+ t1 = _this.line;
+ if (t1 == null)
+ return _this.get$library();
+ t2 = _this.column;
+ if (t2 == null)
+ return _this.get$library() + " " + A.S(t1);
+ return _this.get$library() + " " + A.S(t1) + ":" + A.S(t2);
+ },
+ toString$0(_) {
+ return this.get$location(this) + " in " + A.S(this.member);
+ },
+ get$uri() {
+ return this.uri;
+ },
+ get$line(receiver) {
+ return this.line;
+ },
+ get$column() {
+ return this.column;
+ },
+ get$member() {
+ return this.member;
+ }
+ };
+ A.Frame_Frame$parseVM_closure.prototype = {
+ call$0() {
+ var match, t2, t3, member, uri, lineAndColumn, line, _null = null,
+ t1 = this.frame;
+ if (t1 === "...")
+ return new A.Frame(A._Uri__Uri(_null, _null, _null, _null), _null, _null, "...");
+ match = $.$get$_vmFrame().firstMatch$1(t1);
+ if (match == null)
+ return new A.UnparsedFrame(A._Uri__Uri(_null, "unparsed", _null, _null), t1);
+ t1 = match._match;
+ if (1 >= t1.length)
+ return A.ioore(t1, 1);
+ t2 = t1[1];
+ t2.toString;
+ t3 = $.$get$_asyncBody();
+ t2 = A.stringReplaceAllUnchecked(t2, t3, "<async>");
+ member = A.stringReplaceAllUnchecked(t2, "<anonymous closure>", "<fn>");
+ if (2 >= t1.length)
+ return A.ioore(t1, 2);
+ t2 = t1[2];
+ t3 = t2;
+ t3.toString;
+ if (B.JSString_methods.startsWith$1(t3, "<data:"))
+ uri = A.Uri_Uri$dataFromString("");
+ else {
+ t2 = t2;
+ t2.toString;
+ uri = A.Uri_parse(t2);
+ }
+ if (3 >= t1.length)
+ return A.ioore(t1, 3);
+ lineAndColumn = t1[3].split(":");
+ t1 = lineAndColumn.length;
+ line = t1 > 1 ? A.int_parse(lineAndColumn[1], _null) : _null;
+ return new A.Frame(uri, line, t1 > 2 ? A.int_parse(lineAndColumn[2], _null) : _null, member);
+ },
+ $signature: 6
+ };
+ A.Frame_Frame$parseV8_closure.prototype = {
+ call$0() {
+ var t2, t3, t4, _s4_ = "<fn>",
+ t1 = this.frame,
+ match = $.$get$_v8Frame().firstMatch$1(t1);
+ if (match == null)
+ return new A.UnparsedFrame(A._Uri__Uri(null, "unparsed", null, null), t1);
+ t1 = new A.Frame_Frame$parseV8_closure_parseLocation(t1);
+ t2 = match._match;
+ t3 = t2.length;
+ if (2 >= t3)
+ return A.ioore(t2, 2);
+ t4 = t2[2];
+ if (t4 != null) {
+ t3 = t4;
+ t3.toString;
+ t2 = t2[1];
+ t2.toString;
+ t2 = A.stringReplaceAllUnchecked(t2, "<anonymous>", _s4_);
+ t2 = A.stringReplaceAllUnchecked(t2, "Anonymous function", _s4_);
+ return t1.call$2(t3, A.stringReplaceAllUnchecked(t2, "(anonymous function)", _s4_));
+ } else {
+ if (3 >= t3)
+ return A.ioore(t2, 3);
+ t2 = t2[3];
+ t2.toString;
+ return t1.call$2(t2, _s4_);
+ }
+ },
+ $signature: 6
+ };
+ A.Frame_Frame$parseV8_closure_parseLocation.prototype = {
+ call$2($location, member) {
+ var t2, urlMatch, uri, line, columnMatch, _null = null,
+ t1 = $.$get$_v8EvalLocation(),
+ evalMatch = t1.firstMatch$1($location);
+ for (; evalMatch != null; $location = t2) {
+ t2 = evalMatch._match;
+ if (1 >= t2.length)
+ return A.ioore(t2, 1);
+ t2 = t2[1];
+ t2.toString;
+ evalMatch = t1.firstMatch$1(t2);
+ }
+ if ($location === "native")
+ return new A.Frame(A.Uri_parse("native"), _null, _null, member);
+ urlMatch = $.$get$_v8UrlLocation().firstMatch$1($location);
+ if (urlMatch == null)
+ return new A.UnparsedFrame(A._Uri__Uri(_null, "unparsed", _null, _null), this.frame);
+ t1 = urlMatch._match;
+ if (1 >= t1.length)
+ return A.ioore(t1, 1);
+ t2 = t1[1];
+ t2.toString;
+ uri = A.Frame__uriOrPathToUri(t2);
+ if (2 >= t1.length)
+ return A.ioore(t1, 2);
+ t2 = t1[2];
+ t2.toString;
+ line = A.int_parse(t2, _null);
+ if (3 >= t1.length)
+ return A.ioore(t1, 3);
+ columnMatch = t1[3];
+ return new A.Frame(uri, line, columnMatch != null ? A.int_parse(columnMatch, _null) : _null, member);
+ },
+ $signature: 38
+ };
+ A.Frame_Frame$_parseFirefoxEval_closure.prototype = {
+ call$0() {
+ var t2, member, uri, line, _null = null,
+ t1 = this.frame,
+ match = $.$get$_firefoxEvalLocation().firstMatch$1(t1);
+ if (match == null)
+ return new A.UnparsedFrame(A._Uri__Uri(_null, "unparsed", _null, _null), t1);
+ t1 = match._match;
+ if (1 >= t1.length)
+ return A.ioore(t1, 1);
+ t2 = t1[1];
+ t2.toString;
+ member = A.stringReplaceAllUnchecked(t2, "/<", "");
+ if (2 >= t1.length)
+ return A.ioore(t1, 2);
+ t2 = t1[2];
+ t2.toString;
+ uri = A.Frame__uriOrPathToUri(t2);
+ if (3 >= t1.length)
+ return A.ioore(t1, 3);
+ t1 = t1[3];
+ t1.toString;
+ line = A.int_parse(t1, _null);
+ return new A.Frame(uri, line, _null, member.length === 0 || member === "anonymous" ? "<fn>" : member);
+ },
+ $signature: 6
+ };
+ A.Frame_Frame$parseFirefox_closure.prototype = {
+ call$0() {
+ var t2, t3, t4, uri, member, line, column, _null = null,
+ t1 = this.frame,
+ match = $.$get$_firefoxSafariFrame().firstMatch$1(t1);
+ if (match == null)
+ return new A.UnparsedFrame(A._Uri__Uri(_null, "unparsed", _null, _null), t1);
+ t2 = match._match;
+ if (3 >= t2.length)
+ return A.ioore(t2, 3);
+ t3 = t2[3];
+ t4 = t3;
+ t4.toString;
+ if (B.JSString_methods.contains$1(t4, " line "))
+ return A.Frame_Frame$_parseFirefoxEval(t1);
+ t1 = t3;
+ t1.toString;
+ uri = A.Frame__uriOrPathToUri(t1);
+ t1 = t2.length;
+ if (1 >= t1)
+ return A.ioore(t2, 1);
+ member = t2[1];
+ if (member != null) {
+ if (2 >= t1)
+ return A.ioore(t2, 2);
+ t1 = t2[2];
+ t1.toString;
+ t1 = B.JSString_methods.allMatches$1("/", t1);
+ member += B.JSArray_methods.join$0(A.List_List$filled(t1.get$length(t1), ".<fn>", false, type$.String));
+ if (member === "")
+ member = "<fn>";
+ member = B.JSString_methods.replaceFirst$2(member, $.$get$_initialDot(), "");
+ } else
+ member = "<fn>";
+ if (4 >= t2.length)
+ return A.ioore(t2, 4);
+ t1 = t2[4];
+ if (t1 === "")
+ line = _null;
+ else {
+ t1 = t1;
+ t1.toString;
+ line = A.int_parse(t1, _null);
+ }
+ if (5 >= t2.length)
+ return A.ioore(t2, 5);
+ t1 = t2[5];
+ if (t1 == null || t1 === "")
+ column = _null;
+ else {
+ t1 = t1;
+ t1.toString;
+ column = A.int_parse(t1, _null);
+ }
+ return new A.Frame(uri, line, column, member);
+ },
+ $signature: 6
+ };
+ A.Frame_Frame$parseFriendly_closure.prototype = {
+ call$0() {
+ var t2, uri, line, column, _null = null,
+ t1 = this.frame,
+ match = $.$get$_friendlyFrame().firstMatch$1(t1);
+ if (match == null)
+ throw A.wrapException(A.FormatException$("Couldn't parse package:stack_trace stack trace line '" + t1 + "'.", _null, _null));
+ t1 = match._match;
+ if (1 >= t1.length)
+ return A.ioore(t1, 1);
+ t2 = t1[1];
+ if (t2 === "data:...")
+ uri = A.Uri_Uri$dataFromString("");
+ else {
+ t2 = t2;
+ t2.toString;
+ uri = A.Uri_parse(t2);
+ }
+ if (uri.get$scheme() === "") {
+ t2 = $.$get$context();
+ uri = t2.toUri$1(t2.absolute$15(0, t2.style.pathFromUri$1(A._parseUri(uri)), _null, _null, _null, _null, _null, _null, _null, _null, _null, _null, _null, _null, _null, _null));
+ }
+ if (2 >= t1.length)
+ return A.ioore(t1, 2);
+ t2 = t1[2];
+ if (t2 == null)
+ line = _null;
+ else {
+ t2 = t2;
+ t2.toString;
+ line = A.int_parse(t2, _null);
+ }
+ if (3 >= t1.length)
+ return A.ioore(t1, 3);
+ t2 = t1[3];
+ if (t2 == null)
+ column = _null;
+ else {
+ t2 = t2;
+ t2.toString;
+ column = A.int_parse(t2, _null);
+ }
+ if (4 >= t1.length)
+ return A.ioore(t1, 4);
+ return new A.Frame(uri, line, column, t1[4]);
+ },
+ $signature: 6
+ };
+ A.LazyTrace.prototype = {
+ get$_lazy_trace$_trace() {
+ var result, _this = this,
+ value = _this.__LazyTrace__trace_FI;
+ if (value === $) {
+ result = _this._thunk.call$0();
+ _this.__LazyTrace__trace_FI !== $ && A.throwLateFieldADI("_trace");
+ _this.__LazyTrace__trace_FI = result;
+ value = result;
+ }
+ return value;
+ },
+ get$frames() {
+ return this.get$_lazy_trace$_trace().get$frames();
+ },
+ get$terse() {
+ return new A.LazyTrace(new A.LazyTrace_terse_closure(this));
+ },
+ toString$0(_) {
+ return this.get$_lazy_trace$_trace().toString$0(0);
+ },
+ $isStackTrace: 1,
+ $isTrace: 1
+ };
+ A.LazyTrace_terse_closure.prototype = {
+ call$0() {
+ return this.$this.get$_lazy_trace$_trace().get$terse();
+ },
+ $signature: 22
+ };
+ A.Trace.prototype = {
+ get$terse() {
+ return this.foldFrames$2$terse(new A.Trace_terse_closure(), true);
+ },
+ foldFrames$2$terse(predicate, terse) {
+ var newFrames, t1, t2, t3, _box_0 = {};
+ _box_0.predicate = predicate;
+ type$.bool_Function_Frame._as(predicate);
+ _box_0.predicate = predicate;
+ _box_0.predicate = new A.Trace_foldFrames_closure(predicate);
+ newFrames = A._setArrayType([], type$.JSArray_Frame);
+ for (t1 = this.frames, t2 = A._arrayInstanceType(t1)._eval$1("ReversedListIterable<1>"), t1 = new A.ReversedListIterable(t1, t2), t1 = new A.ListIterator(t1, t1.get$length(t1), t2._eval$1("ListIterator<ListIterable.E>")), t2 = t2._eval$1("ListIterable.E"); t1.moveNext$0();) {
+ t3 = t1.__internal$_current;
+ if (t3 == null)
+ t3 = t2._as(t3);
+ if (t3 instanceof A.UnparsedFrame || !A.boolConversionCheck(_box_0.predicate.call$1(t3)))
+ B.JSArray_methods.add$1(newFrames, t3);
+ else if (newFrames.length === 0 || !A.boolConversionCheck(_box_0.predicate.call$1(B.JSArray_methods.get$last(newFrames))))
+ B.JSArray_methods.add$1(newFrames, new A.Frame(t3.get$uri(), t3.get$line(t3), t3.get$column(), t3.get$member()));
+ }
+ t1 = type$.MappedListIterable_Frame_Frame;
+ newFrames = A.List_List$of(new A.MappedListIterable(newFrames, type$.Frame_Function_Frame._as(new A.Trace_foldFrames_closure0(_box_0)), t1), true, t1._eval$1("ListIterable.E"));
+ if (newFrames.length > 1 && A.boolConversionCheck(_box_0.predicate.call$1(B.JSArray_methods.get$first(newFrames))))
+ B.JSArray_methods.removeAt$1(newFrames, 0);
+ return A.Trace$(new A.ReversedListIterable(newFrames, A._arrayInstanceType(newFrames)._eval$1("ReversedListIterable<1>")), this.original._stackTrace);
+ },
+ toString$0(_) {
+ var t1 = this.frames,
+ t2 = A._arrayInstanceType(t1);
+ return new A.MappedListIterable(t1, t2._eval$1("String(1)")._as(new A.Trace_toString_closure(new A.MappedListIterable(t1, t2._eval$1("int(1)")._as(new A.Trace_toString_closure0()), t2._eval$1("MappedListIterable<1,int>")).fold$1$2(0, 0, B.CONSTANT, type$.int))), t2._eval$1("MappedListIterable<1,String>")).join$0(0);
+ },
+ $isStackTrace: 1,
+ get$frames() {
+ return this.frames;
+ }
+ };
+ A.Trace_Trace$from_closure.prototype = {
+ call$0() {
+ return A.Trace_Trace$parse(this.trace.toString$0(0));
+ },
+ $signature: 22
+ };
+ A.Trace__parseVM_closure.prototype = {
+ call$1(line) {
+ return A._asString(line).length !== 0;
+ },
+ $signature: 1
+ };
+ A.Trace$parseV8_closure.prototype = {
+ call$1(line) {
+ return !B.JSString_methods.startsWith$1(A._asString(line), $.$get$_v8TraceLine());
+ },
+ $signature: 1
+ };
+ A.Trace$parseJSCore_closure.prototype = {
+ call$1(line) {
+ return A._asString(line) !== "\tat ";
+ },
+ $signature: 1
+ };
+ A.Trace$parseFirefox_closure.prototype = {
+ call$1(line) {
+ A._asString(line);
+ return line.length !== 0 && line !== "[native code]";
+ },
+ $signature: 1
+ };
+ A.Trace$parseFriendly_closure.prototype = {
+ call$1(line) {
+ return !B.JSString_methods.startsWith$1(A._asString(line), "=====");
+ },
+ $signature: 1
+ };
+ A.Trace_terse_closure.prototype = {
+ call$1(_) {
+ return false;
+ },
+ $signature: 23
+ };
+ A.Trace_foldFrames_closure.prototype = {
+ call$1(frame) {
+ var t1;
+ if (A.boolConversionCheck(this.oldPredicate.call$1(frame)))
+ return true;
+ if (frame.get$isCore())
+ return true;
+ if (frame.get$$package() === "stack_trace")
+ return true;
+ t1 = frame.get$member();
+ t1.toString;
+ if (!B.JSString_methods.contains$1(t1, "<async>"))
+ return false;
+ return frame.get$line(frame) == null;
+ },
+ $signature: 23
+ };
+ A.Trace_foldFrames_closure0.prototype = {
+ call$1(frame) {
+ var t1, t2;
+ type$.Frame._as(frame);
+ if (frame instanceof A.UnparsedFrame || !A.boolConversionCheck(this._box_0.predicate.call$1(frame)))
+ return frame;
+ t1 = frame.get$library();
+ t2 = $.$get$_terseRegExp();
+ return new A.Frame(A.Uri_parse(A.stringReplaceAllUnchecked(t1, t2, "")), null, null, frame.get$member());
+ },
+ $signature: 62
+ };
+ A.Trace_toString_closure0.prototype = {
+ call$1(frame) {
+ type$.Frame._as(frame);
+ return frame.get$location(frame).length;
+ },
+ $signature: 20
+ };
+ A.Trace_toString_closure.prototype = {
+ call$1(frame) {
+ type$.Frame._as(frame);
+ if (frame instanceof A.UnparsedFrame)
+ return frame.toString$0(0) + "\n";
+ return B.JSString_methods.padRight$1(frame.get$location(frame), this.longest) + " " + A.S(frame.get$member()) + "\n";
+ },
+ $signature: 21
+ };
+ A.UnparsedFrame.prototype = {
+ toString$0(_) {
+ return this.member;
+ },
+ $isFrame: 1,
+ get$uri() {
+ return this.uri;
+ },
+ get$line() {
+ return null;
+ },
+ get$column() {
+ return null;
+ },
+ get$isCore() {
+ return false;
+ },
+ get$library() {
+ return "unparsed";
+ },
+ get$$package() {
+ return null;
+ },
+ get$location() {
+ return "unparsed";
+ },
+ get$member() {
+ return this.member;
+ }
+ };
+ A.GuaranteeChannel.prototype = {
+ GuaranteeChannel$3$allowSinkErrors(innerSink, allowSinkErrors, _box_0, $T) {
+ var _this = this,
+ t1 = _this.$ti,
+ t2 = t1._eval$1("_GuaranteeSink<1>")._as(new A._GuaranteeSink(innerSink, _this, new A._AsyncCompleter(new A._Future($.Zone__current, type$._Future_dynamic), type$._AsyncCompleter_dynamic), true, $T._eval$1("_GuaranteeSink<0>")));
+ _this.__GuaranteeChannel__sink_F !== $ && A.throwLateFieldAI("_sink");
+ _this.set$__GuaranteeChannel__sink_F(t2);
+ t1 = t1._eval$1("StreamController<1>")._as(A.StreamController_StreamController(null, new A.GuaranteeChannel_closure(_box_0, _this, $T), true, $T));
+ _this.__GuaranteeChannel__streamController_F !== $ && A.throwLateFieldAI("_streamController");
+ _this.set$__GuaranteeChannel__streamController_F(t1);
+ },
+ _onSinkDisconnected$0() {
+ var subscription, t1;
+ this._disconnected = true;
+ subscription = this._subscription;
+ if (subscription != null)
+ subscription.cancel$0(0);
+ t1 = this.__GuaranteeChannel__streamController_F;
+ t1 === $ && A.throwLateFieldNI("_streamController");
+ t1.close$0(0);
+ },
+ set$__GuaranteeChannel__sink_F(__GuaranteeChannel__sink_F) {
+ this.__GuaranteeChannel__sink_F = this.$ti._eval$1("_GuaranteeSink<1>")._as(__GuaranteeChannel__sink_F);
+ },
+ set$__GuaranteeChannel__streamController_F(__GuaranteeChannel__streamController_F) {
+ this.__GuaranteeChannel__streamController_F = this.$ti._eval$1("StreamController<1>")._as(__GuaranteeChannel__streamController_F);
+ },
+ set$_subscription(_subscription) {
+ this._subscription = this.$ti._eval$1("StreamSubscription<1>?")._as(_subscription);
+ }
+ };
+ A.GuaranteeChannel_closure.prototype = {
+ call$0() {
+ var t2, t3,
+ t1 = this.$this;
+ if (t1._disconnected)
+ return;
+ t2 = this._box_0.innerStream;
+ t3 = t1.__GuaranteeChannel__streamController_F;
+ t3 === $ && A.throwLateFieldNI("_streamController");
+ t1.set$_subscription(t2.listen$3$onDone$onError(this.T._eval$1("~(0)")._as(t3.get$add(t3)), new A.GuaranteeChannel__closure(t1), t3.get$addError()));
+ },
+ $signature: 0
+ };
+ A.GuaranteeChannel__closure.prototype = {
+ call$0() {
+ var t1 = this.$this,
+ t2 = t1.__GuaranteeChannel__sink_F;
+ t2 === $ && A.throwLateFieldNI("_sink");
+ t2._onStreamDisconnected$0();
+ t1 = t1.__GuaranteeChannel__streamController_F;
+ t1 === $ && A.throwLateFieldNI("_streamController");
+ t1.close$0(0);
+ },
+ $signature: 0
+ };
+ A._GuaranteeSink.prototype = {
+ add$1(_, data) {
+ var t1, _this = this;
+ _this.$ti._precomputed1._as(data);
+ if (_this._closed)
+ throw A.wrapException(A.StateError$("Cannot add event after closing."));
+ if (_this._addStreamSubscription != null)
+ throw A.wrapException(A.StateError$("Cannot add event while adding stream."));
+ if (_this._disconnected)
+ return;
+ t1 = _this._inner;
+ t1._target.add$1(0, t1.$ti._precomputed1._as(data));
+ },
+ addError$2(error, stackTrace) {
+ var _this = this;
+ type$.Object._as(error);
+ type$.nullable_StackTrace._as(stackTrace);
+ if (_this._closed)
+ throw A.wrapException(A.StateError$("Cannot add event after closing."));
+ if (_this._addStreamSubscription != null)
+ throw A.wrapException(A.StateError$("Cannot add event while adding stream."));
+ if (_this._disconnected)
+ return;
+ _this._addError$2(error, stackTrace);
+ },
+ addError$1(error) {
+ return this.addError$2(error, null);
+ },
+ _addError$2(error, stackTrace) {
+ this._inner._target.addError$2(type$.Object._as(error), type$.nullable_StackTrace._as(stackTrace));
+ return;
+ },
+ _addError$1(error) {
+ return this._addError$2(error, null);
+ },
+ addStream$1(_, stream) {
+ var t2, t3, _this = this,
+ t1 = _this.$ti;
+ t1._eval$1("Stream<1>")._as(stream);
+ if (_this._closed)
+ throw A.wrapException(A.StateError$("Cannot add stream after closing."));
+ if (_this._addStreamSubscription != null)
+ throw A.wrapException(A.StateError$("Cannot add stream while adding stream."));
+ if (_this._disconnected)
+ return A.Future_Future$value(null, type$.void);
+ t2 = _this._addStreamCompleter = new A._SyncCompleter(new A._Future($.Zone__current, type$._Future_dynamic), type$._SyncCompleter_dynamic);
+ t3 = _this._inner;
+ _this.set$_addStreamSubscription(stream.listen$3$onDone$onError(t1._eval$1("~(1)")._as(t3.get$add(t3)), type$.void_Function_$opt_dynamic._as(t2.get$complete(t2)), _this.get$_addError()));
+ return _this._addStreamCompleter.future.then$1$1(new A._GuaranteeSink_addStream_closure(_this), type$.void);
+ },
+ close$0(_) {
+ var _this = this;
+ if (_this._addStreamSubscription != null)
+ throw A.wrapException(A.StateError$("Cannot close sink while adding stream."));
+ if (_this._closed)
+ return _this._doneCompleter.future;
+ _this._closed = true;
+ if (!_this._disconnected) {
+ _this._channel._onSinkDisconnected$0();
+ _this._doneCompleter.complete$1(0, _this._inner._target.close$0(0));
+ }
+ return _this._doneCompleter.future;
+ },
+ _onStreamDisconnected$0() {
+ var t1, t2, _this = this;
+ _this._disconnected = true;
+ t1 = _this._doneCompleter;
+ if ((t1.future._async$_state & 30) === 0)
+ t1.complete$0(0);
+ t1 = _this._addStreamSubscription;
+ if (t1 == null)
+ return;
+ t2 = _this._addStreamCompleter;
+ t2.toString;
+ t2.complete$1(0, t1.cancel$0(0));
+ _this._addStreamCompleter = null;
+ _this.set$_addStreamSubscription(null);
+ },
+ set$_addStreamSubscription(_addStreamSubscription) {
+ this._addStreamSubscription = this.$ti._eval$1("StreamSubscription<1>?")._as(_addStreamSubscription);
+ },
+ $isStreamConsumer: 1,
+ $isStreamSink: 1
+ };
+ A._GuaranteeSink_addStream_closure.prototype = {
+ call$1(_) {
+ var t1 = this.$this;
+ t1._addStreamCompleter = null;
+ t1.set$_addStreamSubscription(null);
+ },
+ $signature: 10
+ };
+ A._MultiChannel.prototype = {
+ _MultiChannel$1(inner, $T) {
+ var t2, t3, _this = this,
+ _s17_ = "_streamController",
+ t1 = _this._mainController;
+ _this._controllers.$indexSet(0, 0, t1);
+ t1 = t1.__StreamChannelController__local_F;
+ t1 === $ && A.throwLateFieldNI("_local");
+ t2 = t1.__GuaranteeChannel__streamController_F;
+ t2 === $ && A.throwLateFieldNI(_s17_);
+ new A._ControllerStream(t2, A._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$2$onDone(new A._MultiChannel_closure(_this, $T), new A._MultiChannel_closure0(_this));
+ t2 = _this._multi_channel$_inner.__GuaranteeChannel__streamController_F;
+ t2 === $ && A.throwLateFieldNI(_s17_);
+ t3 = A._instanceType(t2)._eval$1("_ControllerStream<1>");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ _this._innerStreamSubscription = new A.CastStream(new A._ControllerStream(t2, t3), t3._eval$1("CastStream<Stream.T,List<@>>")).listen$3$onDone$onError(new A._MultiChannel_closure1(_this, $T), _this.get$_closeInnerChannel(), t1.get$addError());
+ },
+ virtualChannel$1(id) {
+ var t2, controller, _this = this,
+ _s17_ = "_streamController",
+ _s8_ = "_foreign",
+ t1 = {};
+ t1.outputId = t1.inputId = null;
+ t1.inputId = id;
+ t1.outputId = id + 1;
+ if (_this._multi_channel$_inner == null) {
+ t1 = _this.$ti;
+ t2 = A.Future_Future$value(null, type$.dynamic);
+ return new A.VirtualChannel(_this, new A._EmptyStream(t1._eval$1("_EmptyStream<1>")), new A.NullStreamSink(t2, t1._eval$1("NullStreamSink<1>")), t1._eval$1("VirtualChannel<1>"));
+ }
+ controller = A._Cell$named("controller");
+ if (_this._pendingIds.remove$1(0, id)) {
+ t2 = _this._controllers.$index(0, id);
+ t2.toString;
+ controller._value = t2;
+ } else {
+ t2 = _this._controllers;
+ if (t2.containsKey$1(0, id) || _this._closedIds.contains$1(0, id))
+ throw A.wrapException(A.ArgumentError$("A virtual channel with id " + id + " already exists.", null));
+ else {
+ controller._value = A.StreamChannelController$(true, _this.$ti._precomputed1);
+ t2.$indexSet(0, id, controller._readLocal$0());
+ }
+ }
+ t2 = controller._readLocal$0().__StreamChannelController__local_F;
+ t2 === $ && A.throwLateFieldNI("_local");
+ t2 = t2.__GuaranteeChannel__streamController_F;
+ t2 === $ && A.throwLateFieldNI(_s17_);
+ new A._ControllerStream(t2, A._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$2$onDone(new A._MultiChannel_virtualChannel_closure(t1, _this), new A._MultiChannel_virtualChannel_closure0(t1, _this));
+ t1 = controller._readLocal$0().__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI(_s8_);
+ t1 = t1.__GuaranteeChannel__streamController_F;
+ t1 === $ && A.throwLateFieldNI(_s17_);
+ t2 = controller._readLocal$0().__StreamChannelController__foreign_F;
+ t2 === $ && A.throwLateFieldNI(_s8_);
+ t2 = t2.__GuaranteeChannel__sink_F;
+ t2 === $ && A.throwLateFieldNI("_sink");
+ return new A.VirtualChannel(_this, new A._ControllerStream(t1, A._instanceType(t1)._eval$1("_ControllerStream<1>")), t2, _this.$ti._eval$1("VirtualChannel<1>"));
+ },
+ _closeChannel$2(inputId, outputId) {
+ var t1, t2, _this = this;
+ _this._closedIds.add$1(0, inputId);
+ t1 = _this._controllers;
+ t2 = t1.remove$1(0, inputId);
+ t2.toString;
+ t2 = t2.__StreamChannelController__local_F;
+ t2 === $ && A.throwLateFieldNI("_local");
+ t2 = t2.__GuaranteeChannel__sink_F;
+ t2 === $ && A.throwLateFieldNI("_sink");
+ t2.close$0(0);
+ t2 = _this._multi_channel$_inner;
+ if (t2 == null)
+ return;
+ t2 = t2.__GuaranteeChannel__sink_F;
+ t2 === $ && A.throwLateFieldNI("_sink");
+ t2.add$1(0, A._setArrayType([outputId], type$.JSArray_int));
+ if (t1._length === 0)
+ _this._closeInnerChannel$0();
+ },
+ _closeInnerChannel$0() {
+ var t2, t3, _i, t4, _this = this,
+ t1 = _this._multi_channel$_inner.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t1.close$0(0);
+ _this._innerStreamSubscription._source.cancel$0(0);
+ _this._multi_channel$_inner = null;
+ for (t1 = _this._controllers, t2 = t1.get$values(t1), t2 = A.List_List$of(t2, false, A._instanceType(t2)._eval$1("Iterable.E")), t3 = t2.length, _i = 0; _i < t3; ++_i) {
+ t4 = t2[_i].__StreamChannelController__local_F;
+ t4 === $ && A.throwLateFieldNI("_local");
+ t4 = t4.__GuaranteeChannel__sink_F;
+ t4 === $ && A.throwLateFieldNI("_sink");
+ t4.close$0(0);
+ }
+ t1.clear$0(0);
+ },
+ $isMultiChannel: 1
+ };
+ A._MultiChannel_closure.prototype = {
+ call$1(message) {
+ var t1;
+ this.T._as(message);
+ t1 = this.$this._multi_channel$_inner.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ return t1.add$1(0, [0, message]);
+ },
+ $signature() {
+ return this.T._eval$1("~(0)");
+ }
+ };
+ A._MultiChannel_closure0.prototype = {
+ call$0() {
+ return this.$this._closeChannel$2(0, 0);
+ },
+ $signature: 0
+ };
+ A._MultiChannel_closure1.prototype = {
+ call$1(message) {
+ var t1, id, t2, t3, controller, t4;
+ type$.List_dynamic._as(message);
+ t1 = J.getInterceptor$asx(message);
+ id = B.JSNumber_methods.toInt$0(A._asNum(t1.$index(message, 0)));
+ t2 = this.$this;
+ if (t2._closedIds.contains$1(0, id))
+ return;
+ t3 = this.T;
+ controller = t2._controllers.putIfAbsent$2(0, id, new A._MultiChannel__closure(t2, id, t3));
+ t2 = t1.get$length(message);
+ t4 = controller.__StreamChannelController__local_F;
+ if (t2 > 1) {
+ t4 === $ && A.throwLateFieldNI("_local");
+ t2 = t4.__GuaranteeChannel__sink_F;
+ t2 === $ && A.throwLateFieldNI("_sink");
+ t2.add$1(0, t3._as(t1.$index(message, 1)));
+ } else {
+ t4 === $ && A.throwLateFieldNI("_local");
+ t1 = t4.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t1.close$0(0);
+ }
+ },
+ $signature: 42
+ };
+ A._MultiChannel__closure.prototype = {
+ call$0() {
+ this.$this._pendingIds.add$1(0, this.id);
+ return A.StreamChannelController$(true, this.T);
+ },
+ $signature() {
+ return this.T._eval$1("StreamChannelController<0>()");
+ }
+ };
+ A._MultiChannel_virtualChannel_closure.prototype = {
+ call$1(message) {
+ var t1 = this.$this;
+ t1.$ti._precomputed1._as(message);
+ t1 = t1._multi_channel$_inner.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ return t1.add$1(0, [this._box_0.outputId, message]);
+ },
+ $signature() {
+ return this.$this.$ti._eval$1("~(1)");
+ }
+ };
+ A._MultiChannel_virtualChannel_closure0.prototype = {
+ call$0() {
+ var t1 = this._box_0;
+ return this.$this._closeChannel$2(t1.inputId, t1.outputId);
+ },
+ $signature: 0
+ };
+ A.VirtualChannel.prototype = {$isMultiChannel: 1};
+ A.StreamChannelController.prototype = {
+ set$__StreamChannelController__local_F(__StreamChannelController__local_F) {
+ this.__StreamChannelController__local_F = this.$ti._eval$1("StreamChannel<1>")._as(__StreamChannelController__local_F);
+ },
+ set$__StreamChannelController__foreign_F(__StreamChannelController__foreign_F) {
+ this.__StreamChannelController__foreign_F = this.$ti._eval$1("StreamChannel<1>")._as(__StreamChannelController__foreign_F);
+ }
+ };
+ A.StreamChannelMixin.prototype = {$isStreamChannel: 1};
+ A.MessagePortExtension_get_postMessage_closure.prototype = {
+ call$1(message) {
+ var t2,
+ t1 = A._setArrayType([], type$.JSArray_Object);
+ if (message != null) {
+ t2 = A.jsify(message);
+ t1.push(t2 == null ? type$.Object._as(t2) : t2);
+ }
+ return A.callMethod(this._this, "postMessage", t1, type$.void);
+ },
+ $signature: 7
+ };
+ A.Subscription.prototype = {};
+ A.main_closure.prototype = {
+ call$0() {
+ var play,
+ serverChannel = A._connectToServer(),
+ t1 = serverChannel._mainController.__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI("_foreign");
+ t1 = t1.__GuaranteeChannel__streamController_F;
+ t1 === $ && A.throwLateFieldNI("_streamController");
+ new A._ControllerStream(t1, A._instanceType(t1)._eval$1("_ControllerStream<1>")).listen$1(new A.main__closure(serverChannel));
+ A.Timer_Timer$periodic(new A.Duration(1000000), new A.main__closure0(serverChannel));
+ play = type$.nullable_JavaScriptObject._as(self.document.querySelector("#play"));
+ play.toString;
+ A.EventTargetExtension_addEventListener(play, "click", A.allowInterop(new A.main__closure1(serverChannel), type$.void_Function_JavaScriptObject));
+ t1 = type$.void_Function;
+ self.dartTest = type$.JavaScriptObject._as({resume: A.allowInterop(new A.main__closure2(serverChannel), t1), restartCurrent: A.allowInterop(new A.main__closure3(serverChannel), t1)});
+ },
+ $signature: 3
+ };
+ A.main__closure.prototype = {
+ call$1(message) {
+ var _0_4, _0_3, _0_9, _0_12, _0_15, _0_14, t1, t2, channel, url, id, suiteChannel, _null = null;
+ $label0$0: {
+ _0_4 = A._InitializedCell$named("#0#4", new A.main___closure(message));
+ _0_3 = A._InitializedCell$named("#0#3", new A.main___closure0(message));
+ _0_9 = A._InitializedCell$named("#0#9", new A.main___closure1(message));
+ _0_12 = A._InitializedCell$named("#0#12", new A.main___closure2(message));
+ _0_15 = A._InitializedCell$named("#0#15", new A.main___closure3(message));
+ _0_14 = A._InitializedCell$named("#0#14", new A.main___closure4(message));
+ t1 = type$.Map_dynamic_dynamic._is(message);
+ if (t1) {
+ if (_0_4._readFinal$0() == null)
+ t2 = A.boolConversionCheck(_0_3._readFinal$0());
+ else
+ t2 = true;
+ if (t2)
+ if ("loadSuite" === _0_4._readFinal$0()) {
+ if (_0_9._readFinal$0() == null)
+ t2 = J.containsKey$1$x(message, "channel");
+ else
+ t2 = true;
+ if (t2)
+ if (typeof _0_9._readFinal$0() == "number") {
+ channel = _0_9._readFinal$0();
+ if (_0_12._readFinal$0() == null)
+ t2 = J.containsKey$1$x(message, "url");
+ else
+ t2 = true;
+ if (t2)
+ if (typeof _0_12._readFinal$0() == "string") {
+ url = _0_12._readFinal$0();
+ if (_0_15._readFinal$0() == null)
+ t2 = A.boolConversionCheck(_0_14._readFinal$0());
+ else
+ t2 = true;
+ if (t2)
+ if (typeof _0_15._readFinal$0() == "number") {
+ id = _0_15._readFinal$0();
+ t2 = true;
+ } else {
+ id = _null;
+ t2 = false;
+ }
+ else {
+ id = _null;
+ t2 = false;
+ }
+ } else {
+ id = _null;
+ url = id;
+ t2 = false;
+ }
+ else {
+ id = _null;
+ url = id;
+ t2 = false;
+ }
+ } else {
+ id = _null;
+ url = id;
+ channel = url;
+ t2 = false;
+ }
+ else {
+ id = _null;
+ url = id;
+ channel = url;
+ t2 = false;
+ }
+ } else {
+ id = _null;
+ url = id;
+ channel = url;
+ t2 = false;
+ }
+ else {
+ id = _null;
+ url = id;
+ channel = url;
+ t2 = false;
+ }
+ } else {
+ id = _null;
+ url = id;
+ channel = url;
+ t2 = false;
+ }
+ if (t2) {
+ suiteChannel = this.serverChannel.virtualChannel$1(J.toInt$0$n(channel));
+ t1 = suiteChannel.$ti._eval$1("StreamChannel<1>")._as(A._connectToIframe(url, J.toInt$0$n(id)));
+ t2 = t1.__GuaranteeChannel__sink_F;
+ t2 === $ && A.throwLateFieldNI("_sink");
+ suiteChannel.stream.pipe$1(t2);
+ t1 = t1.__GuaranteeChannel__streamController_F;
+ t1 === $ && A.throwLateFieldNI("_streamController");
+ new A._ControllerStream(t1, A._instanceType(t1)._eval$1("_ControllerStream<1>")).pipe$1(suiteChannel.sink);
+ break $label0$0;
+ }
+ if (t1) {
+ if (_0_4._readFinal$0() == null)
+ t2 = A.boolConversionCheck(_0_3._readFinal$0());
+ else
+ t2 = true;
+ t2 = t2 && "displayPause" === _0_4._readFinal$0();
+ } else
+ t2 = false;
+ if (t2) {
+ type$.JavaScriptObject._as(type$.nullable_JavaScriptObject._as(self.document.body).classList).add("paused");
+ break $label0$0;
+ }
+ if (t1) {
+ if (_0_4._readFinal$0() == null)
+ t2 = A.boolConversionCheck(_0_3._readFinal$0());
+ else
+ t2 = true;
+ t2 = t2 && "resume" === _0_4._readFinal$0();
+ } else
+ t2 = false;
+ if (t2) {
+ type$.JavaScriptObject._as(type$.nullable_JavaScriptObject._as(self.document.body).classList).remove("paused");
+ break $label0$0;
+ }
+ if (t1) {
+ if (_0_4._readFinal$0() == null)
+ t1 = A.boolConversionCheck(_0_3._readFinal$0());
+ else
+ t1 = true;
+ if (t1)
+ if ("closeSuite" === _0_4._readFinal$0()) {
+ if (_0_15._readFinal$0() == null)
+ t1 = A.boolConversionCheck(_0_14._readFinal$0());
+ else
+ t1 = true;
+ if (t1) {
+ id = _0_15._readFinal$0();
+ t1 = true;
+ } else {
+ id = _null;
+ t1 = false;
+ }
+ } else {
+ id = _null;
+ t1 = false;
+ }
+ else {
+ id = _null;
+ t1 = false;
+ }
+ } else {
+ id = _null;
+ t1 = false;
+ }
+ if (t1) {
+ t1 = $._iframes.remove$1(0, id);
+ t2 = type$.nullable_JavaScriptObject;
+ if (t2._as(t1.parentNode) != null)
+ type$.JavaScriptObject._as(t2._as(t1.parentNode).removeChild(t1));
+ t1 = $._subscriptions.remove$1(0, id);
+ if (t1 != null)
+ J.cancel$0$z(t1);
+ t1 = $._domSubscriptions.remove$1(0, id);
+ if (t1 != null)
+ A.EventTargetExtension_removeEventListener(t1.target, t1.type, t1.listener);
+ break $label0$0;
+ }
+ type$.JavaScriptObject._as(self.window.console).warn("Unhandled message from test runner: " + A.S(message));
+ }
+ },
+ $signature: 5
+ };
+ A.main___closure.prototype = {
+ call$0() {
+ return J.$index$asx(type$.Map_dynamic_dynamic._as(this._0_0), "command");
+ },
+ $signature: 2
+ };
+ A.main___closure0.prototype = {
+ call$0() {
+ return J.containsKey$1$x(type$.Map_dynamic_dynamic._as(this._0_0), "command");
+ },
+ $signature: 24
+ };
+ A.main___closure1.prototype = {
+ call$0() {
+ return J.$index$asx(type$.Map_dynamic_dynamic._as(this._0_0), "channel");
+ },
+ $signature: 2
+ };
+ A.main___closure2.prototype = {
+ call$0() {
+ return J.$index$asx(type$.Map_dynamic_dynamic._as(this._0_0), "url");
+ },
+ $signature: 2
+ };
+ A.main___closure3.prototype = {
+ call$0() {
+ return J.$index$asx(type$.Map_dynamic_dynamic._as(this._0_0), "id");
+ },
+ $signature: 2
+ };
+ A.main___closure4.prototype = {
+ call$0() {
+ return J.containsKey$1$x(type$.Map_dynamic_dynamic._as(this._0_0), "id");
+ },
+ $signature: 24
+ };
+ A.main__closure0.prototype = {
+ call$1(_) {
+ var t1, t2;
+ type$.Timer._as(_);
+ t1 = this.serverChannel._mainController.__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI("_foreign");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t2 = type$.String;
+ return t1.add$1(0, A.LinkedHashMap_LinkedHashMap$_literal(["command", "ping"], t2, t2));
+ },
+ $signature: 44
+ };
+ A.main__closure1.prototype = {
+ call$1(_) {
+ var t2,
+ t1 = type$.JavaScriptObject;
+ t1._as(_);
+ t2 = type$.nullable_JavaScriptObject;
+ if (!A._asBool(t1._as(t2._as(self.document.body).classList).contains("paused")))
+ return;
+ t1._as(t2._as(self.document.body).classList).remove("paused");
+ t1 = this.serverChannel._mainController.__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI("_foreign");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t2 = type$.String;
+ t1.add$1(0, A.LinkedHashMap_LinkedHashMap$_literal(["command", "resume"], t2, t2));
+ },
+ $signature: 8
+ };
+ A.main__closure2.prototype = {
+ call$0() {
+ var t1 = type$.nullable_JavaScriptObject,
+ t2 = type$.JavaScriptObject;
+ if (!A._asBool(t2._as(t1._as(self.document.body).classList).contains("paused")))
+ return;
+ t2._as(t1._as(self.document.body).classList).remove("paused");
+ t1 = this.serverChannel._mainController.__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI("_foreign");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t2 = type$.String;
+ t1.add$1(0, A.LinkedHashMap_LinkedHashMap$_literal(["command", "resume"], t2, t2));
+ },
+ $signature: 0
+ };
+ A.main__closure3.prototype = {
+ call$0() {
+ var t2,
+ t1 = this.serverChannel._mainController.__StreamChannelController__foreign_F;
+ t1 === $ && A.throwLateFieldNI("_foreign");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t2 = type$.String;
+ t1.add$1(0, A.LinkedHashMap_LinkedHashMap$_literal(["command", "restart"], t2, t2));
+ },
+ $signature: 0
+ };
+ A.main_closure0.prototype = {
+ call$2(error, stackTrace) {
+ type$.Object._as(error);
+ type$.StackTrace._as(stackTrace);
+ type$.JavaScriptObject._as(self.window.console).warn(A.S(error) + "\n" + A.Trace_Trace$from(stackTrace).get$terse().toString$0(0));
+ },
+ $signature: 11
+ };
+ A._connectToServer_closure.prototype = {
+ call$1(message) {
+ var t1;
+ type$.JavaScriptObject._as(message);
+ t1 = this.controller.__StreamChannelController__local_F;
+ t1 === $ && A.throwLateFieldNI("_local");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t1.add$1(0, B.C_JsonCodec.decode$2$reviver(0, A._asString(A.dartify(message.data)), null));
+ },
+ $signature: 8
+ };
+ A._connectToServer_closure0.prototype = {
+ call$1(message) {
+ return this.webSocket.send(B.C_JsonCodec.encode$2$toEncodable(message, null));
+ },
+ $signature: 5
+ };
+ A._connectToIframe_closure.prototype = {
+ call$1($event) {
+ var t2, _0_0, _0_6, _0_11, port, data, _this = this, _null = null,
+ t1 = type$.JavaScriptObject;
+ t1._as($event);
+ if (A._asString($event.origin) !== A._asString(t1._as(self.window.location).origin))
+ return;
+ t2 = $event.source.location;
+ t2 = t2 == null ? _null : A._asStringQ(t2.href);
+ if (t2 != A._asStringQ(_this.iframe.src))
+ return;
+ $event.stopPropagation();
+ t2 = _this.windowSubscription._readLocal$0();
+ A.EventTargetExtension_removeEventListener(t2.target, t2.type, t2.listener);
+ $label0$0: {
+ _0_0 = A.dartify($event.data);
+ _0_6 = A._InitializedCell$named("#0#6", new A._connectToIframe__closure(_0_0));
+ _0_11 = A._InitializedCell$named("#0#11", new A._connectToIframe__closure0(_0_0));
+ if ("port" === _0_0) {
+ t1._as(self.window.console).log("Connecting channel for suite " + _this.suiteUrl.toString$0(0));
+ t1 = J.cast$1$0$ax(type$.List_dynamic._as($event.ports), t1);
+ port = t1.get$first(t1);
+ t1 = _this.id;
+ t2 = _this.controller;
+ $._domSubscriptions.$indexSet(0, t1, A.Subscription$(port, "message", A.allowInterop(new A._connectToIframe__closure1(t2), type$.void_Function_JavaScriptObject)));
+ port.start();
+ t2 = t2.__StreamChannelController__local_F;
+ t2 === $ && A.throwLateFieldNI("_local");
+ t2 = t2.__GuaranteeChannel__streamController_F;
+ t2 === $ && A.throwLateFieldNI("_streamController");
+ $._subscriptions.$indexSet(0, t1, new A._ControllerStream(t2, A._instanceType(t2)._eval$1("_ControllerStream<1>")).listen$1(A.MessagePortExtension_get_postMessage(port)));
+ break $label0$0;
+ }
+ if (type$.Map_dynamic_dynamic._is(_0_0)) {
+ if (_0_6._readFinal$0() == null)
+ t1 = J.containsKey$1$x(_0_0, "exception");
+ else
+ t1 = true;
+ if (t1)
+ if (true === _0_6._readFinal$0()) {
+ if (_0_11._readFinal$0() == null)
+ t1 = J.containsKey$1$x(_0_0, "data");
+ else
+ t1 = true;
+ if (t1) {
+ data = _0_11._readFinal$0();
+ t1 = true;
+ } else {
+ data = _null;
+ t1 = false;
+ }
+ } else {
+ data = _null;
+ t1 = false;
+ }
+ else {
+ data = _null;
+ t1 = false;
+ }
+ } else {
+ data = _null;
+ t1 = false;
+ }
+ if (t1) {
+ t1 = _this.controller.__StreamChannelController__local_F;
+ t1 === $ && A.throwLateFieldNI("_local");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t1.add$1(0, data);
+ }
+ }
+ },
+ $signature: 8
+ };
+ A._connectToIframe__closure1.prototype = {
+ call$1($event) {
+ var t1;
+ type$.JavaScriptObject._as($event);
+ t1 = this.controller.__StreamChannelController__local_F;
+ t1 === $ && A.throwLateFieldNI("_local");
+ t1 = t1.__GuaranteeChannel__sink_F;
+ t1 === $ && A.throwLateFieldNI("_sink");
+ t1.add$1(0, A.dartify($event.data));
+ },
+ $signature: 8
+ };
+ A._connectToIframe__closure.prototype = {
+ call$0() {
+ return J.$index$asx(type$.Map_dynamic_dynamic._as(this._0_0), "exception");
+ },
+ $signature: 2
+ };
+ A._connectToIframe__closure0.prototype = {
+ call$0() {
+ return J.$index$asx(type$.Map_dynamic_dynamic._as(this._0_0), "data");
+ },
+ $signature: 2
+ };
+ (function aliases() {
+ var _ = J.Interceptor.prototype;
+ _.super$Interceptor$toString = _.toString$0;
+ _ = J.LegacyJavaScriptObject.prototype;
+ _.super$LegacyJavaScriptObject$toString = _.toString$0;
+ _ = A.Iterable.prototype;
+ _.super$Iterable$skipWhile = _.skipWhile$1;
+ })();
+ (function installTearOffs() {
+ var _instance_1_u = hunkHelpers._instance_1u,
+ _static_1 = hunkHelpers._static_1,
+ _static_0 = hunkHelpers._static_0,
+ _static_2 = hunkHelpers._static_2,
+ _static = hunkHelpers.installStaticTearOff,
+ _instance = hunkHelpers.installInstanceTearOff,
+ _instance_2_u = hunkHelpers._instance_2u,
+ _instance_1_i = hunkHelpers._instance_1i,
+ _instance_0_u = hunkHelpers._instance_0u;
+ _instance_1_u(A.CastStreamSubscription.prototype, "get$__internal$_onData", "__internal$_onData$1", 7);
+ _static_1(A, "async__AsyncRun__scheduleImmediateJsOverride$closure", "_AsyncRun__scheduleImmediateJsOverride", 13);
+ _static_1(A, "async__AsyncRun__scheduleImmediateWithSetImmediate$closure", "_AsyncRun__scheduleImmediateWithSetImmediate", 13);
+ _static_1(A, "async__AsyncRun__scheduleImmediateWithTimer$closure", "_AsyncRun__scheduleImmediateWithTimer", 13);
+ _static_0(A, "async___startMicrotaskLoop$closure", "_startMicrotaskLoop", 0);
+ _static_1(A, "async___nullDataHandler$closure", "_nullDataHandler", 5);
+ _static_2(A, "async___nullErrorHandler$closure", "_nullErrorHandler", 11);
+ _static_0(A, "async___nullDoneHandler$closure", "_nullDoneHandler", 0);
+ _static(A, "async___rootHandleUncaughtError$closure", 5, null, ["call$5"], ["_rootHandleUncaughtError"], 47, 0);
+ _static(A, "async___rootRun$closure", 4, null, ["call$1$4", "call$4"], ["_rootRun", function($self, $parent, zone, f) {
+ return A._rootRun($self, $parent, zone, f, type$.dynamic);
+ }], 48, 1);
+ _static(A, "async___rootRunUnary$closure", 5, null, ["call$2$5", "call$5"], ["_rootRunUnary", function($self, $parent, zone, f, arg) {
+ return A._rootRunUnary($self, $parent, zone, f, arg, type$.dynamic, type$.dynamic);
+ }], 49, 1);
+ _static(A, "async___rootRunBinary$closure", 6, null, ["call$3$6", "call$6"], ["_rootRunBinary", function($self, $parent, zone, f, arg1, arg2) {
+ return A._rootRunBinary($self, $parent, zone, f, arg1, arg2, type$.dynamic, type$.dynamic, type$.dynamic);
+ }], 50, 1);
+ _static(A, "async___rootRegisterCallback$closure", 4, null, ["call$1$4", "call$4"], ["_rootRegisterCallback", function($self, $parent, zone, f) {
+ return A._rootRegisterCallback($self, $parent, zone, f, type$.dynamic);
+ }], 51, 0);
+ _static(A, "async___rootRegisterUnaryCallback$closure", 4, null, ["call$2$4", "call$4"], ["_rootRegisterUnaryCallback", function($self, $parent, zone, f) {
+ return A._rootRegisterUnaryCallback($self, $parent, zone, f, type$.dynamic, type$.dynamic);
+ }], 52, 0);
+ _static(A, "async___rootRegisterBinaryCallback$closure", 4, null, ["call$3$4", "call$4"], ["_rootRegisterBinaryCallback", function($self, $parent, zone, f) {
+ return A._rootRegisterBinaryCallback($self, $parent, zone, f, type$.dynamic, type$.dynamic, type$.dynamic);
+ }], 53, 0);
+ _static(A, "async___rootErrorCallback$closure", 5, null, ["call$5"], ["_rootErrorCallback"], 54, 0);
+ _static(A, "async___rootScheduleMicrotask$closure", 4, null, ["call$4"], ["_rootScheduleMicrotask"], 55, 0);
+ _static(A, "async___rootCreateTimer$closure", 5, null, ["call$5"], ["_rootCreateTimer"], 56, 0);
+ _static(A, "async___rootCreatePeriodicTimer$closure", 5, null, ["call$5"], ["_rootCreatePeriodicTimer"], 57, 0);
+ _static(A, "async___rootPrint$closure", 4, null, ["call$4"], ["_rootPrint"], 58, 0);
+ _static(A, "async___rootFork$closure", 5, null, ["call$5"], ["_rootFork"], 59, 0);
+ _instance(A._SyncCompleter.prototype, "get$complete", 1, 0, function() {
+ return [null];
+ }, ["call$1", "call$0"], ["complete$1", "complete$0"], 43, 0, 0);
+ _instance_2_u(A._Future.prototype, "get$_completeError", "_completeError$2", 11);
+ var _;
+ _instance_1_i(_ = A._StreamController.prototype, "get$add", "add$1", 7);
+ _instance(_, "get$addError", 0, 1, function() {
+ return [null];
+ }, ["call$2", "call$1"], ["addError$2", "addError$1"], 12, 0, 0);
+ _instance_1_i(A._StreamSinkWrapper.prototype, "get$add", "add$1", 7);
+ _instance_0_u(A._DoneStreamSubscription.prototype, "get$_onMicrotask", "_onMicrotask$0", 0);
+ _static_1(A, "convert___defaultToEncodable$closure", "_defaultToEncodable", 15);
+ _static_1(A, "core_Uri_decodeComponent$closure", "Uri_decodeComponent", 17);
+ _static_1(A, "frame_Frame___parseVM_tearOff$closure", "Frame___parseVM_tearOff", 9);
+ _static_1(A, "frame_Frame___parseV8_tearOff$closure", "Frame___parseV8_tearOff", 9);
+ _static_1(A, "frame_Frame___parseFirefox_tearOff$closure", "Frame___parseFirefox_tearOff", 9);
+ _static_1(A, "frame_Frame___parseFriendly_tearOff$closure", "Frame___parseFriendly_tearOff", 9);
+ _static_1(A, "trace_Trace___parseVM_tearOff$closure", "Trace___parseVM_tearOff", 14);
+ _static_1(A, "trace_Trace___parseFriendly_tearOff$closure", "Trace___parseFriendly_tearOff", 14);
+ _instance(_ = A._GuaranteeSink.prototype, "get$addError", 0, 1, function() {
+ return [null];
+ }, ["call$2", "call$1"], ["addError$2", "addError$1"], 12, 0, 0);
+ _instance(_, "get$_addError", 0, 1, function() {
+ return [null];
+ }, ["call$2", "call$1"], ["_addError$2", "_addError$1"], 12, 0, 0);
+ _instance_0_u(A._MultiChannel.prototype, "get$_closeInnerChannel", "_closeInnerChannel$0", 0);
+ _static(A, "math__max$closure", 2, null, ["call$1$2", "call$2"], ["max", function(a, b) {
+ return A.max(a, b, type$.num);
+ }], 41, 0);
+ })();
+ (function inheritance() {
+ var _mixin = hunkHelpers.mixin,
+ _inherit = hunkHelpers.inherit,
+ _inheritMany = hunkHelpers.inheritMany;
+ _inherit(A.Object, null);
+ _inheritMany(A.Object, [A.JS_CONST, J.Interceptor, J.ArrayIterator, A.Stream, A.CastStreamSubscription, A.Iterable, A.CastIterator, A.Error, A.ListBase, A.Closure, A.SentinelValue, A.ListIterator, A.MappedIterator, A.WhereIterator, A.ExpandIterator, A.TakeIterator, A.SkipIterator, A.SkipWhileIterator, A.EmptyIterator, A.WhereTypeIterator, A.FixedLengthListMixin, A.UnmodifiableListMixin, A.Symbol, A.MapView, A.ConstantMap, A._KeysOrValuesOrElementsIterator, A.JSInvocationMirror, A.TypeErrorDecoder, A.NullThrownFromJavaScriptException, A._StackTrace, A._Required, A.MapBase, A.LinkedHashMapCell, A.LinkedHashMapKeyIterator, A.JSSyntaxRegExp, A._MatchImplementation, A._AllMatchesIterator, A.StringMatch, A._StringAllMatchesIterator, A._Cell, A._InitializedCell, A.Rti, A._FunctionParameters, A._Type, A._TimerImpl, A.AsyncError, A._Completer, A._FutureListener, A._Future, A._AsyncCallbackEntry, A._StreamController, A._SyncStreamControllerDispatch, A._BufferingStreamSubscription, A._StreamSinkWrapper, A._DelayedEvent, A._DelayedDone, A._PendingEvents, A._DoneStreamSubscription, A._ZoneFunction, A._ZoneSpecification, A._ZoneDelegate, A._Zone, A._HashMapKeyIterator, A.SetBase, A._LinkedHashSetCell, A._LinkedHashSetIterator, A._UnmodifiableMapMixin, A.Codec, A.Converter, A._JsonStringifier, A._Utf8Encoder, A._Utf8Decoder, A.DateTime, A.Duration, A.OutOfMemoryError, A.StackOverflowError, A._Exception, A.FormatException, A.Null, A._StringStackTrace, A.StringBuffer, A._Uri, A.UriData, A._SimpleUri, A.CssStyleDeclarationBase, A.ImmutableListMixin, A.FixedSizeListIterator, A.NullRejectionException, A.NullStreamSink, A.Context, A.Style, A.ParsedPath, A.PathException, A.Chain, A.Frame, A.LazyTrace, A.Trace, A.UnparsedFrame, A.StreamChannelMixin, A._GuaranteeSink, A.StreamChannelController, A.Subscription]);
+ _inheritMany(J.Interceptor, [J.JSBool, J.JSNull, J.JavaScriptObject, J.JavaScriptBigInt, J.JavaScriptSymbol, J.JSNumber, J.JSString]);
+ _inheritMany(J.JavaScriptObject, [J.LegacyJavaScriptObject, J.JSArray, A.NativeByteBuffer, A.NativeTypedData, A.EventTarget, A.AccessibleNodeList, A.Blob, A.CssTransformComponent, A.CssRule, A._CssStyleDeclaration_JavaScriptObject_CssStyleDeclarationBase, A.CssStyleValue, A.DataTransferItemList, A.DomException, A._DomRectList_JavaScriptObject_ListMixin, A.DomRectReadOnly, A._DomStringList_JavaScriptObject_ListMixin, A.DomTokenList, A._FileList_JavaScriptObject_ListMixin, A.Gamepad, A.History, A._HtmlCollection_JavaScriptObject_ListMixin, A.Location, A.MediaList, A._MidiInputMap_JavaScriptObject_MapMixin, A._MidiOutputMap_JavaScriptObject_MapMixin, A.MimeType, A._MimeTypeArray_JavaScriptObject_ListMixin, A._NodeList_JavaScriptObject_ListMixin, A.Plugin, A._PluginArray_JavaScriptObject_ListMixin, A._RtcStatsReport_JavaScriptObject_MapMixin, A.SpeechGrammar, A._SpeechGrammarList_JavaScriptObject_ListMixin, A.SpeechRecognitionResult, A._Storage_JavaScriptObject_MapMixin, A.StyleSheet, A._TextTrackCueList_JavaScriptObject_ListMixin, A.TimeRanges, A.Touch, A._TouchList_JavaScriptObject_ListMixin, A.TrackDefaultList, A.Url, A.__CssRuleList_JavaScriptObject_ListMixin, A.__GamepadList_JavaScriptObject_ListMixin, A.__NamedNodeMap_JavaScriptObject_ListMixin, A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin, A.__StyleSheetList_JavaScriptObject_ListMixin, A.Length, A._LengthList_JavaScriptObject_ListMixin, A.Number, A._NumberList_JavaScriptObject_ListMixin, A.PointList, A._StringList_JavaScriptObject_ListMixin, A.Transform, A._TransformList_JavaScriptObject_ListMixin, A.AudioBuffer, A._AudioParamMap_JavaScriptObject_MapMixin]);
+ _inheritMany(J.LegacyJavaScriptObject, [J.PlainJavaScriptObject, J.UnknownJavaScriptObject, J.JavaScriptFunction]);
+ _inherit(J.JSUnmodifiableArray, J.JSArray);
+ _inheritMany(J.JSNumber, [J.JSInt, J.JSNumNotInt]);
+ _inheritMany(A.Stream, [A.CastStream, A._StreamImpl, A._EmptyStream]);
+ _inheritMany(A.Iterable, [A._CastIterableBase, A.EfficientLengthIterable, A.MappedIterable, A.WhereIterable, A.ExpandIterable, A.TakeIterable, A.SkipIterable, A.SkipWhileIterable, A.WhereTypeIterable, A._KeysOrValues, A._AllMatchesIterable, A._StringAllMatchesIterable]);
+ _inheritMany(A._CastIterableBase, [A.CastIterable, A.__CastListBase__CastIterableBase_ListMixin]);
+ _inherit(A._EfficientLengthCastIterable, A.CastIterable);
+ _inherit(A._CastListBase, A.__CastListBase__CastIterableBase_ListMixin);
+ _inherit(A.CastList, A._CastListBase);
+ _inheritMany(A.Error, [A.LateError, A.TypeError, A.JsNoSuchMethodError, A.UnknownJsTypeError, A._CyclicInitializationError, A.RuntimeError, A.AssertionError, A._Error, A.JsonUnsupportedObjectError, A.ArgumentError, A.NoSuchMethodError, A.UnsupportedError, A.UnimplementedError, A.StateError, A.ConcurrentModificationError]);
+ _inherit(A.UnmodifiableListBase, A.ListBase);
+ _inherit(A.CodeUnits, A.UnmodifiableListBase);
+ _inheritMany(A.Closure, [A.Closure0Args, A.Instantiation, A.Closure2Args, A.TearOffClosure, A.JsLinkedHashMap_values_closure, A.initHooks_closure, A.initHooks_closure1, A._AsyncRun__initializeScheduleImmediate_internalCallback, A._AsyncRun__initializeScheduleImmediate_closure, A._Future__chainForeignFuture_closure, A._Future__propagateToListeners_handleWhenCompleteCallback_closure, A.Stream_pipe_closure, A.Stream_length_closure, A._CustomZone_bindUnaryCallback_closure, A._CustomZone_bindUnaryCallbackGuarded_closure, A._RootZone_bindUnaryCallback_closure, A._RootZone_bindUnaryCallbackGuarded_closure, A.runZonedGuarded_closure, A._Uri__makePath_closure, A._createTables_setChars, A._createTables_setRange, A.jsify__convert, A.promiseToFuture_closure, A.promiseToFuture_closure0, A.dartify_convert, A.Context_joinAll_closure, A.Context_split_closure, A._validateArgList_closure, A.WindowsStyle_absolutePathToUri_closure, A.Chain_Chain$parse_closure, A.Chain_toTrace_closure, A.Chain_toString_closure0, A.Chain_toString__closure0, A.Chain_toString_closure, A.Chain_toString__closure, A.Trace__parseVM_closure, A.Trace$parseV8_closure, A.Trace$parseJSCore_closure, A.Trace$parseFirefox_closure, A.Trace$parseFriendly_closure, A.Trace_terse_closure, A.Trace_foldFrames_closure, A.Trace_foldFrames_closure0, A.Trace_toString_closure0, A.Trace_toString_closure, A._GuaranteeSink_addStream_closure, A._MultiChannel_closure, A._MultiChannel_closure1, A._MultiChannel_virtualChannel_closure, A.MessagePortExtension_get_postMessage_closure, A.main__closure, A.main__closure0, A.main__closure1, A._connectToServer_closure, A._connectToServer_closure0, A._connectToIframe_closure, A._connectToIframe__closure1]);
+ _inheritMany(A.Closure0Args, [A.nullFuture_closure, A._AsyncRun__scheduleImmediateJsOverride_internalCallback, A._AsyncRun__scheduleImmediateWithSetImmediate_internalCallback, A._TimerImpl_internalCallback, A._TimerImpl$periodic_closure, A._Future__addListener_closure, A._Future__prependListeners_closure, A._Future__chainForeignFuture_closure1, A._Future__chainCoreFutureAsync_closure, A._Future__asyncCompleteWithValue_closure, A._Future__asyncCompleteError_closure, A._Future__propagateToListeners_handleWhenCompleteCallback, A._Future__propagateToListeners_handleValueCallback, A._Future__propagateToListeners_handleError, A.Stream_length_closure0, A._StreamController__subscribe_closure, A._StreamController__recordCancel_complete, A._AddStreamState_cancel_closure, A._BufferingStreamSubscription__sendError_sendError, A._BufferingStreamSubscription__sendDone_sendDone, A._PendingEvents_schedule_closure, A._CustomZone_bindCallback_closure, A._CustomZone_bindCallbackGuarded_closure, A._rootHandleError_closure, A._RootZone_bindCallback_closure, A._RootZone_bindCallbackGuarded_closure, A.Utf8Decoder__decoder_closure, A.Utf8Decoder__decoderNonfatal_closure, A.NullStreamSink_addStream_closure, A.Frame_Frame$parseVM_closure, A.Frame_Frame$parseV8_closure, A.Frame_Frame$_parseFirefoxEval_closure, A.Frame_Frame$parseFirefox_closure, A.Frame_Frame$parseFriendly_closure, A.LazyTrace_terse_closure, A.Trace_Trace$from_closure, A.GuaranteeChannel_closure, A.GuaranteeChannel__closure, A._MultiChannel_closure0, A._MultiChannel__closure, A._MultiChannel_virtualChannel_closure0, A.main_closure, A.main___closure, A.main___closure0, A.main___closure1, A.main___closure2, A.main___closure3, A.main___closure4, A.main__closure2, A.main__closure3, A._connectToIframe__closure, A._connectToIframe__closure0]);
+ _inheritMany(A.EfficientLengthIterable, [A.ListIterable, A.EmptyIterable, A.LinkedHashMapKeyIterable, A._HashMapKeyIterable]);
+ _inheritMany(A.ListIterable, [A.SubListIterable, A.MappedListIterable, A.ReversedListIterable, A._JsonMapKeyIterable]);
+ _inherit(A.EfficientLengthMappedIterable, A.MappedIterable);
+ _inherit(A.EfficientLengthTakeIterable, A.TakeIterable);
+ _inherit(A.EfficientLengthSkipIterable, A.SkipIterable);
+ _inherit(A._UnmodifiableMapView_MapView__UnmodifiableMapMixin, A.MapView);
+ _inherit(A.UnmodifiableMapView, A._UnmodifiableMapView_MapView__UnmodifiableMapMixin);
+ _inherit(A.ConstantMapView, A.UnmodifiableMapView);
+ _inherit(A.ConstantStringMap, A.ConstantMap);
+ _inherit(A.Instantiation1, A.Instantiation);
+ _inheritMany(A.Closure2Args, [A.Primitives_functionNoSuchMethod_closure, A.initHooks_closure0, A._Future__chainForeignFuture_closure0, A.MapBase_mapToString_closure, A._JsonStringifier_writeMap_closure, A.NoSuchMethodError_toString_closure, A.Uri_splitQueryString_closure, A.Uri__parseIPv4Address_error, A.Uri_parseIPv6Address_error, A.Uri_parseIPv6Address_parseHex, A._createTables_build, A.MidiInputMap_keys_closure, A.MidiOutputMap_keys_closure, A.RtcStatsReport_keys_closure, A.Storage_keys_closure, A.AudioParamMap_keys_closure, A.Frame_Frame$parseV8_closure_parseLocation, A.main_closure0]);
+ _inherit(A.NullError, A.TypeError);
+ _inheritMany(A.TearOffClosure, [A.StaticClosure, A.BoundClosure]);
+ _inherit(A._AssertionError, A.AssertionError);
+ _inheritMany(A.MapBase, [A.JsLinkedHashMap, A._HashMap, A._JsonMap]);
+ _inheritMany(A.NativeTypedData, [A.NativeByteData, A.NativeTypedArray]);
+ _inheritMany(A.NativeTypedArray, [A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin, A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin]);
+ _inherit(A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin, A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin);
+ _inherit(A.NativeTypedArrayOfDouble, A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin);
+ _inherit(A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin, A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin);
+ _inherit(A.NativeTypedArrayOfInt, A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin);
+ _inheritMany(A.NativeTypedArrayOfDouble, [A.NativeFloat32List, A.NativeFloat64List]);
+ _inheritMany(A.NativeTypedArrayOfInt, [A.NativeInt16List, A.NativeInt32List, A.NativeInt8List, A.NativeUint16List, A.NativeUint32List, A.NativeUint8ClampedList, A.NativeUint8List]);
+ _inherit(A._TypeError, A._Error);
+ _inheritMany(A._Completer, [A._AsyncCompleter, A._SyncCompleter]);
+ _inherit(A._SyncStreamController, A._StreamController);
+ _inherit(A._ControllerStream, A._StreamImpl);
+ _inherit(A._ControllerSubscription, A._BufferingStreamSubscription);
+ _inheritMany(A._DelayedEvent, [A._DelayedData, A._DelayedError]);
+ _inheritMany(A._Zone, [A._CustomZone, A._RootZone]);
+ _inherit(A._IdentityHashMap, A._HashMap);
+ _inherit(A._SetBase, A.SetBase);
+ _inherit(A._LinkedHashSet, A._SetBase);
+ _inheritMany(A.Codec, [A.Encoding, A.Base64Codec, A._FusedCodec, A.JsonCodec]);
+ _inheritMany(A.Encoding, [A.AsciiCodec, A.Utf8Codec]);
+ _inheritMany(A.Converter, [A._UnicodeSubsetEncoder, A.Base64Encoder, A.JsonEncoder, A.JsonDecoder, A.Utf8Encoder, A.Utf8Decoder]);
+ _inherit(A.AsciiEncoder, A._UnicodeSubsetEncoder);
+ _inherit(A.JsonCyclicError, A.JsonUnsupportedObjectError);
+ _inherit(A._JsonStringStringifier, A._JsonStringifier);
+ _inheritMany(A.ArgumentError, [A.RangeError, A.IndexError]);
+ _inherit(A._DataUri, A._Uri);
+ _inheritMany(A.EventTarget, [A.Node, A.FileWriter, A.SourceBuffer, A._SourceBufferList_EventTarget_ListMixin, A.TextTrack, A.TextTrackCue, A._TextTrackList_EventTarget_ListMixin, A.VideoTrackList, A.AudioTrackList, A.BaseAudioContext]);
+ _inheritMany(A.Node, [A.Element, A.CharacterData]);
+ _inherit(A.HtmlElement, A.Element);
+ _inheritMany(A.HtmlElement, [A.AnchorElement, A.AreaElement, A.FormElement, A.SelectElement]);
+ _inherit(A.CssPerspective, A.CssTransformComponent);
+ _inherit(A.CssStyleDeclaration, A._CssStyleDeclaration_JavaScriptObject_CssStyleDeclarationBase);
+ _inheritMany(A.CssStyleValue, [A.CssTransformValue, A.CssUnparsedValue]);
+ _inherit(A._DomRectList_JavaScriptObject_ListMixin_ImmutableListMixin, A._DomRectList_JavaScriptObject_ListMixin);
+ _inherit(A.DomRectList, A._DomRectList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._DomStringList_JavaScriptObject_ListMixin_ImmutableListMixin, A._DomStringList_JavaScriptObject_ListMixin);
+ _inherit(A.DomStringList, A._DomStringList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.File, A.Blob);
+ _inherit(A._FileList_JavaScriptObject_ListMixin_ImmutableListMixin, A._FileList_JavaScriptObject_ListMixin);
+ _inherit(A.FileList, A._FileList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._HtmlCollection_JavaScriptObject_ListMixin_ImmutableListMixin, A._HtmlCollection_JavaScriptObject_ListMixin);
+ _inherit(A.HtmlCollection, A._HtmlCollection_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.MidiInputMap, A._MidiInputMap_JavaScriptObject_MapMixin);
+ _inherit(A.MidiOutputMap, A._MidiOutputMap_JavaScriptObject_MapMixin);
+ _inherit(A._MimeTypeArray_JavaScriptObject_ListMixin_ImmutableListMixin, A._MimeTypeArray_JavaScriptObject_ListMixin);
+ _inherit(A.MimeTypeArray, A._MimeTypeArray_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._NodeList_JavaScriptObject_ListMixin_ImmutableListMixin, A._NodeList_JavaScriptObject_ListMixin);
+ _inherit(A.NodeList, A._NodeList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._PluginArray_JavaScriptObject_ListMixin_ImmutableListMixin, A._PluginArray_JavaScriptObject_ListMixin);
+ _inherit(A.PluginArray, A._PluginArray_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.RtcStatsReport, A._RtcStatsReport_JavaScriptObject_MapMixin);
+ _inherit(A._SourceBufferList_EventTarget_ListMixin_ImmutableListMixin, A._SourceBufferList_EventTarget_ListMixin);
+ _inherit(A.SourceBufferList, A._SourceBufferList_EventTarget_ListMixin_ImmutableListMixin);
+ _inherit(A._SpeechGrammarList_JavaScriptObject_ListMixin_ImmutableListMixin, A._SpeechGrammarList_JavaScriptObject_ListMixin);
+ _inherit(A.SpeechGrammarList, A._SpeechGrammarList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.Storage, A._Storage_JavaScriptObject_MapMixin);
+ _inherit(A._TextTrackCueList_JavaScriptObject_ListMixin_ImmutableListMixin, A._TextTrackCueList_JavaScriptObject_ListMixin);
+ _inherit(A.TextTrackCueList, A._TextTrackCueList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._TextTrackList_EventTarget_ListMixin_ImmutableListMixin, A._TextTrackList_EventTarget_ListMixin);
+ _inherit(A.TextTrackList, A._TextTrackList_EventTarget_ListMixin_ImmutableListMixin);
+ _inherit(A._TouchList_JavaScriptObject_ListMixin_ImmutableListMixin, A._TouchList_JavaScriptObject_ListMixin);
+ _inherit(A.TouchList, A._TouchList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.__CssRuleList_JavaScriptObject_ListMixin_ImmutableListMixin, A.__CssRuleList_JavaScriptObject_ListMixin);
+ _inherit(A._CssRuleList, A.__CssRuleList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._DomRect, A.DomRectReadOnly);
+ _inherit(A.__GamepadList_JavaScriptObject_ListMixin_ImmutableListMixin, A.__GamepadList_JavaScriptObject_ListMixin);
+ _inherit(A._GamepadList, A.__GamepadList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.__NamedNodeMap_JavaScriptObject_ListMixin_ImmutableListMixin, A.__NamedNodeMap_JavaScriptObject_ListMixin);
+ _inherit(A._NamedNodeMap, A.__NamedNodeMap_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin_ImmutableListMixin, A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin);
+ _inherit(A._SpeechRecognitionResultList, A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.__StyleSheetList_JavaScriptObject_ListMixin_ImmutableListMixin, A.__StyleSheetList_JavaScriptObject_ListMixin);
+ _inherit(A._StyleSheetList, A.__StyleSheetList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._LengthList_JavaScriptObject_ListMixin_ImmutableListMixin, A._LengthList_JavaScriptObject_ListMixin);
+ _inherit(A.LengthList, A._LengthList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._NumberList_JavaScriptObject_ListMixin_ImmutableListMixin, A._NumberList_JavaScriptObject_ListMixin);
+ _inherit(A.NumberList, A._NumberList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._StringList_JavaScriptObject_ListMixin_ImmutableListMixin, A._StringList_JavaScriptObject_ListMixin);
+ _inherit(A.StringList, A._StringList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A._TransformList_JavaScriptObject_ListMixin_ImmutableListMixin, A._TransformList_JavaScriptObject_ListMixin);
+ _inherit(A.TransformList, A._TransformList_JavaScriptObject_ListMixin_ImmutableListMixin);
+ _inherit(A.AudioParamMap, A._AudioParamMap_JavaScriptObject_MapMixin);
+ _inherit(A.OfflineAudioContext, A.BaseAudioContext);
+ _inherit(A.InternalStyle, A.Style);
+ _inheritMany(A.InternalStyle, [A.PosixStyle, A.UrlStyle, A.WindowsStyle]);
+ _inheritMany(A.StreamChannelMixin, [A.GuaranteeChannel, A._MultiChannel, A.VirtualChannel]);
+ _mixin(A.UnmodifiableListBase, A.UnmodifiableListMixin);
+ _mixin(A.__CastListBase__CastIterableBase_ListMixin, A.ListBase);
+ _mixin(A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin, A.ListBase);
+ _mixin(A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin, A.FixedLengthListMixin);
+ _mixin(A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin, A.ListBase);
+ _mixin(A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin, A.FixedLengthListMixin);
+ _mixin(A._SyncStreamController, A._SyncStreamControllerDispatch);
+ _mixin(A._UnmodifiableMapView_MapView__UnmodifiableMapMixin, A._UnmodifiableMapMixin);
+ _mixin(A._CssStyleDeclaration_JavaScriptObject_CssStyleDeclarationBase, A.CssStyleDeclarationBase);
+ _mixin(A._DomRectList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._DomRectList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._DomStringList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._DomStringList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._FileList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._FileList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._HtmlCollection_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._HtmlCollection_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._MidiInputMap_JavaScriptObject_MapMixin, A.MapBase);
+ _mixin(A._MidiOutputMap_JavaScriptObject_MapMixin, A.MapBase);
+ _mixin(A._MimeTypeArray_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._MimeTypeArray_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._NodeList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._NodeList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._PluginArray_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._PluginArray_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._RtcStatsReport_JavaScriptObject_MapMixin, A.MapBase);
+ _mixin(A._SourceBufferList_EventTarget_ListMixin, A.ListBase);
+ _mixin(A._SourceBufferList_EventTarget_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._SpeechGrammarList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._SpeechGrammarList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._Storage_JavaScriptObject_MapMixin, A.MapBase);
+ _mixin(A._TextTrackCueList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._TextTrackCueList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._TextTrackList_EventTarget_ListMixin, A.ListBase);
+ _mixin(A._TextTrackList_EventTarget_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._TouchList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._TouchList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A.__CssRuleList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A.__CssRuleList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A.__GamepadList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A.__GamepadList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A.__NamedNodeMap_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A.__NamedNodeMap_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A.__SpeechRecognitionResultList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A.__StyleSheetList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A.__StyleSheetList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._LengthList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._LengthList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._NumberList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._NumberList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._StringList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._StringList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._TransformList_JavaScriptObject_ListMixin, A.ListBase);
+ _mixin(A._TransformList_JavaScriptObject_ListMixin_ImmutableListMixin, A.ImmutableListMixin);
+ _mixin(A._AudioParamMap_JavaScriptObject_MapMixin, A.MapBase);
+ })();
+ var init = {
+ typeUniverse: {eC: new Map(), tR: {}, eT: {}, tPV: {}, sEA: []},
+ mangledGlobalNames: {int: "int", double: "double", num: "num", String: "String", bool: "bool", Null: "Null", List: "List"},
+ mangledNames: {},
+ types: ["~()", "bool(String)", "@()", "Null()", "~(String,@)", "~(@)", "Frame()", "~(Object?)", "~(JavaScriptObject)", "Frame(String)", "Null(@)", "~(Object,StackTrace)", "~(Object[StackTrace?])", "~(~())", "Trace(String)", "@(@)", "~(Object?,Object?)", "String(String)", "~(Uint8List,String,int)", "Object?(Object?)", "int(Frame)", "String(Frame)", "Trace()", "bool(Frame)", "bool()", "~(String,int)", "Null(Object,StackTrace)", "~(String,String)", "_Future<@>(@)", "Future<@>(@)", "Null(~())", "String(String?)", "List<Frame>(Trace)", "int(Trace)", "~(Zone,ZoneDelegate,Zone,Object,StackTrace)", "String(Trace)", "@(@,String)", "@(String)", "Frame(String,String)", "~(Symbol0,@)", "Map<String,String>(Map<String,String>,String)", "0^(0^,0^)<num>", "~(List<@>)", "~([Object?])", "~(Timer)", "~(String,int?)", "int(int,int)", "~(Zone?,ZoneDelegate?,Zone,Object,StackTrace)", "0^(Zone?,ZoneDelegate?,Zone,0^())<Object?>", "0^(Zone?,ZoneDelegate?,Zone,0^(1^),1^)<Object?,Object?>", "0^(Zone?,ZoneDelegate?,Zone,0^(1^,2^),1^,2^)<Object?,Object?,Object?>", "0^()(Zone,ZoneDelegate,Zone,0^())<Object?>", "0^(1^)(Zone,ZoneDelegate,Zone,0^(1^))<Object?,Object?>", "0^(1^,2^)(Zone,ZoneDelegate,Zone,0^(1^,2^))<Object?,Object?,Object?>", "AsyncError?(Zone,ZoneDelegate,Zone,Object,StackTrace?)", "~(Zone?,ZoneDelegate?,Zone,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~())", "Timer(Zone,ZoneDelegate,Zone,Duration,~(Timer))", "~(Zone,ZoneDelegate,Zone,String)", "Zone(Zone?,ZoneDelegate?,Zone,ZoneSpecification?,Map<Object?,Object?>?)", "Future<Null>()", "Uint8List(@,@)", "Frame(Frame)"],
+ interceptorsByTag: null,
+ leafTags: null,
+ arrayRti: Symbol("$ti")
+ };
+ A._Universe_addRules(init.typeUniverse, JSON.parse('{"PlainJavaScriptObject":"LegacyJavaScriptObject","UnknownJavaScriptObject":"LegacyJavaScriptObject","JavaScriptFunction":"LegacyJavaScriptObject","AbortPaymentEvent":"JavaScriptObject","ExtendableEvent":"JavaScriptObject","Event":"JavaScriptObject","AudioContext":"BaseAudioContext","AbsoluteOrientationSensor":"EventTarget","OrientationSensor":"EventTarget","Sensor":"EventTarget","MathMLElement":"Element","AudioElement":"HtmlElement","MediaElement":"HtmlElement","HtmlDocument":"Node","Document":"Node","VttCue":"TextTrackCue","CDataSection":"CharacterData","Text":"CharacterData","HtmlFormControlsCollection":"HtmlCollection","CssCharsetRule":"CssRule","CssMatrixComponent":"CssTransformComponent","CssStyleSheet":"StyleSheet","CssurlImageValue":"CssStyleValue","CssImageValue":"CssStyleValue","CssResourceValue":"CssStyleValue","JSBool":{"bool":[],"TrustedGetRuntimeType":[]},"JSNull":{"Null":[],"TrustedGetRuntimeType":[]},"LegacyJavaScriptObject":{"JavaScriptObject":[]},"JSArray":{"List":["1"],"JavaScriptObject":[],"EfficientLengthIterable":["1"],"Iterable":["1"]},"JSUnmodifiableArray":{"JSArray":["1"],"List":["1"],"JavaScriptObject":[],"EfficientLengthIterable":["1"],"Iterable":["1"]},"ArrayIterator":{"Iterator":["1"]},"JSNumber":{"double":[],"num":[]},"JSInt":{"double":[],"int":[],"num":[],"TrustedGetRuntimeType":[]},"JSNumNotInt":{"double":[],"num":[],"TrustedGetRuntimeType":[]},"JSString":{"String":[],"Pattern":[],"TrustedGetRuntimeType":[]},"CastStream":{"Stream":["2"],"Stream.T":"2"},"CastStreamSubscription":{"StreamSubscription":["2"]},"_CastIterableBase":{"Iterable":["2"]},"CastIterator":{"Iterator":["2"]},"CastIterable":{"_CastIterableBase":["1","2"],"Iterable":["2"],"Iterable.E":"2"},"_EfficientLengthCastIterable":{"CastIterable":["1","2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2"},"_CastListBase":{"ListBase":["2"],"List":["2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"]},"CastList":{"_CastListBase":["1","2"],"ListBase":["2"],"List":["2"],"_CastIterableBase":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListBase.E":"2","Iterable.E":"2"},"LateError":{"Error":[]},"CodeUnits":{"ListBase":["int"],"UnmodifiableListMixin":["int"],"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"],"ListBase.E":"int","UnmodifiableListMixin.E":"int"},"EfficientLengthIterable":{"Iterable":["1"]},"ListIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"SubListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"ListIterator":{"Iterator":["1"]},"MappedIterable":{"Iterable":["2"],"Iterable.E":"2"},"EfficientLengthMappedIterable":{"MappedIterable":["1","2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"Iterable.E":"2"},"MappedIterator":{"Iterator":["2"]},"MappedListIterable":{"ListIterable":["2"],"EfficientLengthIterable":["2"],"Iterable":["2"],"ListIterable.E":"2","Iterable.E":"2"},"WhereIterable":{"Iterable":["1"],"Iterable.E":"1"},"WhereIterator":{"Iterator":["1"]},"ExpandIterable":{"Iterable":["2"],"Iterable.E":"2"},"ExpandIterator":{"Iterator":["2"]},"TakeIterable":{"Iterable":["1"],"Iterable.E":"1"},"EfficientLengthTakeIterable":{"TakeIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"TakeIterator":{"Iterator":["1"]},"SkipIterable":{"Iterable":["1"],"Iterable.E":"1"},"EfficientLengthSkipIterable":{"SkipIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"SkipIterator":{"Iterator":["1"]},"SkipWhileIterable":{"Iterable":["1"],"Iterable.E":"1"},"SkipWhileIterator":{"Iterator":["1"]},"EmptyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"EmptyIterator":{"Iterator":["1"]},"WhereTypeIterable":{"Iterable":["1"],"Iterable.E":"1"},"WhereTypeIterator":{"Iterator":["1"]},"UnmodifiableListBase":{"ListBase":["1"],"UnmodifiableListMixin":["1"],"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"ReversedListIterable":{"ListIterable":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"],"ListIterable.E":"1","Iterable.E":"1"},"Symbol":{"Symbol0":[]},"ConstantMapView":{"UnmodifiableMapView":["1","2"],"_UnmodifiableMapView_MapView__UnmodifiableMapMixin":["1","2"],"MapView":["1","2"],"_UnmodifiableMapMixin":["1","2"],"Map":["1","2"]},"ConstantMap":{"Map":["1","2"]},"ConstantStringMap":{"ConstantMap":["1","2"],"Map":["1","2"]},"_KeysOrValues":{"Iterable":["1"],"Iterable.E":"1"},"_KeysOrValuesOrElementsIterator":{"Iterator":["1"]},"Instantiation":{"Closure":[],"Function":[]},"Instantiation1":{"Closure":[],"Function":[]},"JSInvocationMirror":{"Invocation":[]},"NullError":{"TypeError":[],"Error":[]},"JsNoSuchMethodError":{"Error":[]},"UnknownJsTypeError":{"Error":[]},"NullThrownFromJavaScriptException":{"Exception":[]},"_StackTrace":{"StackTrace":[]},"Closure":{"Function":[]},"Closure0Args":{"Closure":[],"Function":[]},"Closure2Args":{"Closure":[],"Function":[]},"TearOffClosure":{"Closure":[],"Function":[]},"StaticClosure":{"Closure":[],"Function":[]},"BoundClosure":{"Closure":[],"Function":[]},"_CyclicInitializationError":{"Error":[]},"RuntimeError":{"Error":[]},"_AssertionError":{"Error":[]},"JsLinkedHashMap":{"MapBase":["1","2"],"LinkedHashMap":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"LinkedHashMapKeyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"LinkedHashMapKeyIterator":{"Iterator":["1"]},"JSSyntaxRegExp":{"RegExp":[],"Pattern":[]},"_MatchImplementation":{"RegExpMatch":[],"Match":[]},"_AllMatchesIterable":{"Iterable":["RegExpMatch"],"Iterable.E":"RegExpMatch"},"_AllMatchesIterator":{"Iterator":["RegExpMatch"]},"StringMatch":{"Match":[]},"_StringAllMatchesIterable":{"Iterable":["Match"],"Iterable.E":"Match"},"_StringAllMatchesIterator":{"Iterator":["Match"]},"NativeByteBuffer":{"JavaScriptObject":[],"ByteBuffer":[],"TrustedGetRuntimeType":[]},"NativeTypedData":{"JavaScriptObject":[]},"NativeByteData":{"JavaScriptObject":[],"ByteData":[],"TrustedGetRuntimeType":[]},"NativeTypedArray":{"JavaScriptIndexingBehavior":["1"],"JavaScriptObject":[]},"NativeTypedArrayOfDouble":{"ListBase":["double"],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"]},"NativeTypedArrayOfInt":{"ListBase":["int"],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"]},"NativeFloat32List":{"ListBase":["double"],"Float32List":[],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double"},"NativeFloat64List":{"ListBase":["double"],"Float64List":[],"List":["double"],"JavaScriptIndexingBehavior":["double"],"JavaScriptObject":[],"EfficientLengthIterable":["double"],"Iterable":["double"],"FixedLengthListMixin":["double"],"TrustedGetRuntimeType":[],"ListBase.E":"double"},"NativeInt16List":{"ListBase":["int"],"Int16List":[],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeInt32List":{"ListBase":["int"],"Int32List":[],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeInt8List":{"ListBase":["int"],"Int8List":[],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint16List":{"ListBase":["int"],"Uint16List":[],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint32List":{"ListBase":["int"],"Uint32List":[],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint8ClampedList":{"ListBase":["int"],"Uint8ClampedList":[],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"NativeUint8List":{"ListBase":["int"],"Uint8List":[],"List":["int"],"JavaScriptIndexingBehavior":["int"],"JavaScriptObject":[],"EfficientLengthIterable":["int"],"Iterable":["int"],"FixedLengthListMixin":["int"],"TrustedGetRuntimeType":[],"ListBase.E":"int"},"_Error":{"Error":[]},"_TypeError":{"TypeError":[],"Error":[]},"AsyncError":{"Error":[]},"_Future":{"Future":["1"]},"_TimerImpl":{"Timer":[]},"_Completer":{"Completer":["1"]},"_AsyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_SyncCompleter":{"_Completer":["1"],"Completer":["1"]},"_StreamController":{"StreamController":["1"],"StreamSink":["1"],"StreamConsumer":["1"],"_StreamControllerLifecycle":["1"],"_EventDispatch":["1"]},"_SyncStreamController":{"_SyncStreamControllerDispatch":["1"],"_StreamController":["1"],"StreamController":["1"],"StreamSink":["1"],"StreamConsumer":["1"],"_StreamControllerLifecycle":["1"],"_EventDispatch":["1"]},"_ControllerStream":{"_StreamImpl":["1"],"Stream":["1"],"Stream.T":"1"},"_ControllerSubscription":{"_BufferingStreamSubscription":["1"],"StreamSubscription":["1"],"_EventDispatch":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamSinkWrapper":{"StreamSink":["1"],"StreamConsumer":["1"]},"_BufferingStreamSubscription":{"StreamSubscription":["1"],"_EventDispatch":["1"],"_BufferingStreamSubscription.T":"1"},"_StreamImpl":{"Stream":["1"]},"_DelayedData":{"_DelayedEvent":["1"]},"_DelayedError":{"_DelayedEvent":["@"]},"_DelayedDone":{"_DelayedEvent":["@"]},"_DoneStreamSubscription":{"StreamSubscription":["1"]},"_EmptyStream":{"Stream":["1"],"Stream.T":"1"},"_ZoneSpecification":{"ZoneSpecification":[]},"_ZoneDelegate":{"ZoneDelegate":[]},"_Zone":{"Zone":[]},"_CustomZone":{"_Zone":[],"Zone":[]},"_RootZone":{"_Zone":[],"Zone":[]},"_HashMap":{"MapBase":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_IdentityHashMap":{"_HashMap":["1","2"],"MapBase":["1","2"],"Map":["1","2"],"MapBase.K":"1","MapBase.V":"2"},"_HashMapKeyIterable":{"EfficientLengthIterable":["1"],"Iterable":["1"],"Iterable.E":"1"},"_HashMapKeyIterator":{"Iterator":["1"]},"_LinkedHashSet":{"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_LinkedHashSetIterator":{"Iterator":["1"]},"ListBase":{"List":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"MapBase":{"Map":["1","2"]},"MapView":{"Map":["1","2"]},"UnmodifiableMapView":{"_UnmodifiableMapView_MapView__UnmodifiableMapMixin":["1","2"],"MapView":["1","2"],"_UnmodifiableMapMixin":["1","2"],"Map":["1","2"]},"SetBase":{"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_SetBase":{"SetBase":["1"],"Set":["1"],"EfficientLengthIterable":["1"],"Iterable":["1"]},"_JsonMap":{"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"_JsonMapKeyIterable":{"ListIterable":["String"],"EfficientLengthIterable":["String"],"Iterable":["String"],"ListIterable.E":"String","Iterable.E":"String"},"AsciiCodec":{"Codec":["String","List<int>"]},"_UnicodeSubsetEncoder":{"Converter":["String","List<int>"],"StreamTransformer":["String","List<int>"]},"AsciiEncoder":{"Converter":["String","List<int>"],"StreamTransformer":["String","List<int>"]},"Base64Codec":{"Codec":["List<int>","String"]},"Base64Encoder":{"Converter":["List<int>","String"],"StreamTransformer":["List<int>","String"]},"_FusedCodec":{"Codec":["1","3"]},"Converter":{"StreamTransformer":["1","2"]},"Encoding":{"Codec":["String","List<int>"]},"JsonUnsupportedObjectError":{"Error":[]},"JsonCyclicError":{"Error":[]},"JsonCodec":{"Codec":["Object?","String"]},"JsonEncoder":{"Converter":["Object?","String"],"StreamTransformer":["Object?","String"]},"JsonDecoder":{"Converter":["String","Object?"],"StreamTransformer":["String","Object?"]},"Utf8Codec":{"Codec":["String","List<int>"]},"Utf8Encoder":{"Converter":["String","List<int>"],"StreamTransformer":["String","List<int>"]},"Utf8Decoder":{"Converter":["List<int>","String"],"StreamTransformer":["List<int>","String"]},"double":{"num":[]},"int":{"num":[]},"List":{"EfficientLengthIterable":["1"],"Iterable":["1"]},"RegExpMatch":{"Match":[]},"String":{"Pattern":[]},"AssertionError":{"Error":[]},"TypeError":{"Error":[]},"ArgumentError":{"Error":[]},"RangeError":{"Error":[]},"IndexError":{"Error":[]},"NoSuchMethodError":{"Error":[]},"UnsupportedError":{"Error":[]},"UnimplementedError":{"Error":[]},"StateError":{"Error":[]},"ConcurrentModificationError":{"Error":[]},"OutOfMemoryError":{"Error":[]},"StackOverflowError":{"Error":[]},"_Exception":{"Exception":[]},"FormatException":{"Exception":[]},"_StringStackTrace":{"StackTrace":[]},"StringBuffer":{"StringSink":[]},"_Uri":{"Uri":[]},"_SimpleUri":{"Uri":[]},"_DataUri":{"Uri":[]},"CssRule":{"JavaScriptObject":[]},"File":{"JavaScriptObject":[]},"Gamepad":{"JavaScriptObject":[]},"MimeType":{"JavaScriptObject":[]},"Node":{"JavaScriptObject":[]},"Plugin":{"JavaScriptObject":[]},"SourceBuffer":{"JavaScriptObject":[]},"SpeechGrammar":{"JavaScriptObject":[]},"SpeechRecognitionResult":{"JavaScriptObject":[]},"StyleSheet":{"JavaScriptObject":[]},"TextTrack":{"JavaScriptObject":[]},"TextTrackCue":{"JavaScriptObject":[]},"Touch":{"JavaScriptObject":[]},"HtmlElement":{"Node":[],"JavaScriptObject":[]},"AccessibleNodeList":{"JavaScriptObject":[]},"AnchorElement":{"Node":[],"JavaScriptObject":[]},"AreaElement":{"Node":[],"JavaScriptObject":[]},"Blob":{"JavaScriptObject":[]},"CharacterData":{"Node":[],"JavaScriptObject":[]},"CssPerspective":{"JavaScriptObject":[]},"CssStyleDeclaration":{"JavaScriptObject":[]},"CssStyleValue":{"JavaScriptObject":[]},"CssTransformComponent":{"JavaScriptObject":[]},"CssTransformValue":{"JavaScriptObject":[]},"CssUnparsedValue":{"JavaScriptObject":[]},"DataTransferItemList":{"JavaScriptObject":[]},"DomException":{"JavaScriptObject":[]},"DomRectList":{"ListBase":["Rectangle<num>"],"ImmutableListMixin":["Rectangle<num>"],"List":["Rectangle<num>"],"JavaScriptIndexingBehavior":["Rectangle<num>"],"JavaScriptObject":[],"EfficientLengthIterable":["Rectangle<num>"],"Iterable":["Rectangle<num>"],"ImmutableListMixin.E":"Rectangle<num>","ListBase.E":"Rectangle<num>"},"DomRectReadOnly":{"JavaScriptObject":[],"Rectangle":["num"]},"DomStringList":{"ListBase":["String"],"ImmutableListMixin":["String"],"List":["String"],"JavaScriptIndexingBehavior":["String"],"JavaScriptObject":[],"EfficientLengthIterable":["String"],"Iterable":["String"],"ImmutableListMixin.E":"String","ListBase.E":"String"},"DomTokenList":{"JavaScriptObject":[]},"Element":{"Node":[],"JavaScriptObject":[]},"EventTarget":{"JavaScriptObject":[]},"FileList":{"ListBase":["File"],"ImmutableListMixin":["File"],"List":["File"],"JavaScriptIndexingBehavior":["File"],"JavaScriptObject":[],"EfficientLengthIterable":["File"],"Iterable":["File"],"ImmutableListMixin.E":"File","ListBase.E":"File"},"FileWriter":{"JavaScriptObject":[]},"FormElement":{"Node":[],"JavaScriptObject":[]},"History":{"JavaScriptObject":[]},"HtmlCollection":{"ListBase":["Node"],"ImmutableListMixin":["Node"],"List":["Node"],"JavaScriptIndexingBehavior":["Node"],"JavaScriptObject":[],"EfficientLengthIterable":["Node"],"Iterable":["Node"],"ImmutableListMixin.E":"Node","ListBase.E":"Node"},"Location":{"JavaScriptObject":[]},"MediaList":{"JavaScriptObject":[]},"MidiInputMap":{"JavaScriptObject":[],"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"MidiOutputMap":{"JavaScriptObject":[],"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"MimeTypeArray":{"ListBase":["MimeType"],"ImmutableListMixin":["MimeType"],"List":["MimeType"],"JavaScriptIndexingBehavior":["MimeType"],"JavaScriptObject":[],"EfficientLengthIterable":["MimeType"],"Iterable":["MimeType"],"ImmutableListMixin.E":"MimeType","ListBase.E":"MimeType"},"NodeList":{"ListBase":["Node"],"ImmutableListMixin":["Node"],"List":["Node"],"JavaScriptIndexingBehavior":["Node"],"JavaScriptObject":[],"EfficientLengthIterable":["Node"],"Iterable":["Node"],"ImmutableListMixin.E":"Node","ListBase.E":"Node"},"PluginArray":{"ListBase":["Plugin"],"ImmutableListMixin":["Plugin"],"List":["Plugin"],"JavaScriptIndexingBehavior":["Plugin"],"JavaScriptObject":[],"EfficientLengthIterable":["Plugin"],"Iterable":["Plugin"],"ImmutableListMixin.E":"Plugin","ListBase.E":"Plugin"},"RtcStatsReport":{"JavaScriptObject":[],"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"SelectElement":{"Node":[],"JavaScriptObject":[]},"SourceBufferList":{"ListBase":["SourceBuffer"],"ImmutableListMixin":["SourceBuffer"],"List":["SourceBuffer"],"JavaScriptIndexingBehavior":["SourceBuffer"],"JavaScriptObject":[],"EfficientLengthIterable":["SourceBuffer"],"Iterable":["SourceBuffer"],"ImmutableListMixin.E":"SourceBuffer","ListBase.E":"SourceBuffer"},"SpeechGrammarList":{"ListBase":["SpeechGrammar"],"ImmutableListMixin":["SpeechGrammar"],"List":["SpeechGrammar"],"JavaScriptIndexingBehavior":["SpeechGrammar"],"JavaScriptObject":[],"EfficientLengthIterable":["SpeechGrammar"],"Iterable":["SpeechGrammar"],"ImmutableListMixin.E":"SpeechGrammar","ListBase.E":"SpeechGrammar"},"Storage":{"JavaScriptObject":[],"MapBase":["String","String"],"Map":["String","String"],"MapBase.K":"String","MapBase.V":"String"},"TextTrackCueList":{"ListBase":["TextTrackCue"],"ImmutableListMixin":["TextTrackCue"],"List":["TextTrackCue"],"JavaScriptIndexingBehavior":["TextTrackCue"],"JavaScriptObject":[],"EfficientLengthIterable":["TextTrackCue"],"Iterable":["TextTrackCue"],"ImmutableListMixin.E":"TextTrackCue","ListBase.E":"TextTrackCue"},"TextTrackList":{"ListBase":["TextTrack"],"ImmutableListMixin":["TextTrack"],"List":["TextTrack"],"JavaScriptIndexingBehavior":["TextTrack"],"JavaScriptObject":[],"EfficientLengthIterable":["TextTrack"],"Iterable":["TextTrack"],"ImmutableListMixin.E":"TextTrack","ListBase.E":"TextTrack"},"TimeRanges":{"JavaScriptObject":[]},"TouchList":{"ListBase":["Touch"],"ImmutableListMixin":["Touch"],"List":["Touch"],"JavaScriptIndexingBehavior":["Touch"],"JavaScriptObject":[],"EfficientLengthIterable":["Touch"],"Iterable":["Touch"],"ImmutableListMixin.E":"Touch","ListBase.E":"Touch"},"TrackDefaultList":{"JavaScriptObject":[]},"Url":{"JavaScriptObject":[]},"VideoTrackList":{"JavaScriptObject":[]},"_CssRuleList":{"ListBase":["CssRule"],"ImmutableListMixin":["CssRule"],"List":["CssRule"],"JavaScriptIndexingBehavior":["CssRule"],"JavaScriptObject":[],"EfficientLengthIterable":["CssRule"],"Iterable":["CssRule"],"ImmutableListMixin.E":"CssRule","ListBase.E":"CssRule"},"_DomRect":{"JavaScriptObject":[],"Rectangle":["num"]},"_GamepadList":{"ListBase":["Gamepad?"],"ImmutableListMixin":["Gamepad?"],"List":["Gamepad?"],"JavaScriptIndexingBehavior":["Gamepad?"],"JavaScriptObject":[],"EfficientLengthIterable":["Gamepad?"],"Iterable":["Gamepad?"],"ImmutableListMixin.E":"Gamepad?","ListBase.E":"Gamepad?"},"_NamedNodeMap":{"ListBase":["Node"],"ImmutableListMixin":["Node"],"List":["Node"],"JavaScriptIndexingBehavior":["Node"],"JavaScriptObject":[],"EfficientLengthIterable":["Node"],"Iterable":["Node"],"ImmutableListMixin.E":"Node","ListBase.E":"Node"},"_SpeechRecognitionResultList":{"ListBase":["SpeechRecognitionResult"],"ImmutableListMixin":["SpeechRecognitionResult"],"List":["SpeechRecognitionResult"],"JavaScriptIndexingBehavior":["SpeechRecognitionResult"],"JavaScriptObject":[],"EfficientLengthIterable":["SpeechRecognitionResult"],"Iterable":["SpeechRecognitionResult"],"ImmutableListMixin.E":"SpeechRecognitionResult","ListBase.E":"SpeechRecognitionResult"},"_StyleSheetList":{"ListBase":["StyleSheet"],"ImmutableListMixin":["StyleSheet"],"List":["StyleSheet"],"JavaScriptIndexingBehavior":["StyleSheet"],"JavaScriptObject":[],"EfficientLengthIterable":["StyleSheet"],"Iterable":["StyleSheet"],"ImmutableListMixin.E":"StyleSheet","ListBase.E":"StyleSheet"},"FixedSizeListIterator":{"Iterator":["1"]},"NullRejectionException":{"Exception":[]},"Length":{"JavaScriptObject":[]},"Number":{"JavaScriptObject":[]},"Transform":{"JavaScriptObject":[]},"LengthList":{"ListBase":["Length"],"ImmutableListMixin":["Length"],"List":["Length"],"JavaScriptObject":[],"EfficientLengthIterable":["Length"],"Iterable":["Length"],"ImmutableListMixin.E":"Length","ListBase.E":"Length"},"NumberList":{"ListBase":["Number"],"ImmutableListMixin":["Number"],"List":["Number"],"JavaScriptObject":[],"EfficientLengthIterable":["Number"],"Iterable":["Number"],"ImmutableListMixin.E":"Number","ListBase.E":"Number"},"PointList":{"JavaScriptObject":[]},"StringList":{"ListBase":["String"],"ImmutableListMixin":["String"],"List":["String"],"JavaScriptObject":[],"EfficientLengthIterable":["String"],"Iterable":["String"],"ImmutableListMixin.E":"String","ListBase.E":"String"},"TransformList":{"ListBase":["Transform"],"ImmutableListMixin":["Transform"],"List":["Transform"],"JavaScriptObject":[],"EfficientLengthIterable":["Transform"],"Iterable":["Transform"],"ImmutableListMixin.E":"Transform","ListBase.E":"Transform"},"AudioBuffer":{"JavaScriptObject":[]},"AudioParamMap":{"JavaScriptObject":[],"MapBase":["String","@"],"Map":["String","@"],"MapBase.K":"String","MapBase.V":"@"},"AudioTrackList":{"JavaScriptObject":[]},"BaseAudioContext":{"JavaScriptObject":[]},"OfflineAudioContext":{"JavaScriptObject":[]},"NullStreamSink":{"StreamSink":["1"],"StreamConsumer":["1"]},"PathException":{"Exception":[]},"PosixStyle":{"InternalStyle":[]},"UrlStyle":{"InternalStyle":[]},"WindowsStyle":{"InternalStyle":[]},"Chain":{"StackTrace":[]},"LazyTrace":{"Trace":[],"StackTrace":[]},"Trace":{"StackTrace":[]},"UnparsedFrame":{"Frame":[]},"GuaranteeChannel":{"StreamChannelMixin":["1"],"StreamChannel":["1"]},"_GuaranteeSink":{"StreamSink":["1"],"StreamConsumer":["1"]},"_MultiChannel":{"StreamChannelMixin":["1"],"MultiChannel":["1"],"StreamChannel":["1"]},"VirtualChannel":{"StreamChannelMixin":["1"],"MultiChannel":["1"],"StreamChannel":["1"]},"StreamChannelMixin":{"StreamChannel":["1"]},"Int8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint8ClampedList":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint16List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Int32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Uint32List":{"List":["int"],"EfficientLengthIterable":["int"],"Iterable":["int"]},"Float32List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]},"Float64List":{"List":["double"],"EfficientLengthIterable":["double"],"Iterable":["double"]}}'));
+ A._Universe_addErasedTypes(init.typeUniverse, JSON.parse('{"UnmodifiableListBase":1,"__CastListBase__CastIterableBase_ListMixin":2,"NativeTypedArray":1,"_DelayedEvent":1,"_SetBase":1}'));
+ var string$ = {
+ x27_has_: "' has been assigned during initialization.",
+ x3d_____: "===== asynchronous gap ===========================\n",
+ Cannotff: "Cannot extract a file path from a URI with a fragment component",
+ Cannotfq: "Cannot extract a file path from a URI with a query component",
+ Cannotn: "Cannot extract a non-Windows file path from a file URI with an authority",
+ Error_: "Error handler must accept one Object or one Object and a StackTrace as arguments, and return a value of the returned future's type",
+ handle: "handleError callback must take either an Object (the error), or both an Object (the error) and a StackTrace."
+ };
+ var type$ = (function rtii() {
+ var findType = A.findType;
+ return {
+ AsyncError: findType("AsyncError"),
+ ByteBuffer: findType("ByteBuffer"),
+ ByteData: findType("ByteData"),
+ ConstantMapView_Symbol_dynamic: findType("ConstantMapView<Symbol0,@>"),
+ CssRule: findType("CssRule"),
+ Duration: findType("Duration"),
+ EfficientLengthIterable_dynamic: findType("EfficientLengthIterable<@>"),
+ Error: findType("Error"),
+ Exception: findType("Exception"),
+ File: findType("File"),
+ Float32List: findType("Float32List"),
+ Float64List: findType("Float64List"),
+ Frame: findType("Frame"),
+ Frame_Function_Frame: findType("Frame(Frame)"),
+ Frame_Function_String: findType("Frame(String)"),
+ Function: findType("Function"),
+ Future_dynamic: findType("Future<@>"),
+ Int16List: findType("Int16List"),
+ Int32List: findType("Int32List"),
+ Int8List: findType("Int8List"),
+ Invocation: findType("Invocation"),
+ Iterable_String: findType("Iterable<String>"),
+ Iterable_dynamic: findType("Iterable<@>"),
+ Iterable_nullable_Object: findType("Iterable<Object?>"),
+ JSArray_Frame: findType("JSArray<Frame>"),
+ JSArray_Object: findType("JSArray<Object>"),
+ JSArray_String: findType("JSArray<String>"),
+ JSArray_Trace: findType("JSArray<Trace>"),
+ JSArray_Uint8List: findType("JSArray<Uint8List>"),
+ JSArray_dynamic: findType("JSArray<@>"),
+ JSArray_int: findType("JSArray<int>"),
+ JSArray_nullable_String: findType("JSArray<String?>"),
+ JSNull: findType("JSNull"),
+ JavaScriptFunction: findType("JavaScriptFunction"),
+ JavaScriptIndexingBehavior_dynamic: findType("JavaScriptIndexingBehavior<@>"),
+ JavaScriptObject: findType("JavaScriptObject"),
+ JsLinkedHashMap_Symbol_dynamic: findType("JsLinkedHashMap<Symbol0,@>"),
+ Length: findType("Length"),
+ List_String: findType("List<String>"),
+ List_dynamic: findType("List<@>"),
+ List_int: findType("List<int>"),
+ Map_String_String: findType("Map<String,String>"),
+ Map_dynamic_dynamic: findType("Map<@,@>"),
+ Map_of_nullable_Object_and_nullable_Object: findType("Map<Object?,Object?>"),
+ MappedIterable_String_Frame: findType("MappedIterable<String,Frame>"),
+ MappedListIterable_Frame_Frame: findType("MappedListIterable<Frame,Frame>"),
+ MappedListIterable_String_Trace: findType("MappedListIterable<String,Trace>"),
+ MappedListIterable_String_dynamic: findType("MappedListIterable<String,@>"),
+ MimeType: findType("MimeType"),
+ NativeUint8List: findType("NativeUint8List"),
+ Node: findType("Node"),
+ Null: findType("Null"),
+ Number: findType("Number"),
+ Object: findType("Object"),
+ Plugin: findType("Plugin"),
+ Record: findType("Record"),
+ Rectangle_num: findType("Rectangle<num>"),
+ RegExpMatch: findType("RegExpMatch"),
+ SourceBuffer: findType("SourceBuffer"),
+ SpeechGrammar: findType("SpeechGrammar"),
+ SpeechRecognitionResult: findType("SpeechRecognitionResult"),
+ StackTrace: findType("StackTrace"),
+ String: findType("String"),
+ StyleSheet: findType("StyleSheet"),
+ Symbol: findType("Symbol0"),
+ TextTrack: findType("TextTrack"),
+ TextTrackCue: findType("TextTrackCue"),
+ Timer: findType("Timer"),
+ Touch: findType("Touch"),
+ Trace: findType("Trace"),
+ Trace_Function_String: findType("Trace(String)"),
+ Transform: findType("Transform"),
+ TrustedGetRuntimeType: findType("TrustedGetRuntimeType"),
+ TypeError: findType("TypeError"),
+ Uint16List: findType("Uint16List"),
+ Uint32List: findType("Uint32List"),
+ Uint8ClampedList: findType("Uint8ClampedList"),
+ Uint8List: findType("Uint8List"),
+ UnknownJavaScriptObject: findType("UnknownJavaScriptObject"),
+ UnmodifiableMapView_String_String: findType("UnmodifiableMapView<String,String>"),
+ Uri: findType("Uri"),
+ WhereIterable_String: findType("WhereIterable<String>"),
+ WhereTypeIterable_String: findType("WhereTypeIterable<String>"),
+ Zone: findType("Zone"),
+ _AsyncCompleter_dynamic: findType("_AsyncCompleter<@>"),
+ _Future_dynamic: findType("_Future<@>"),
+ _Future_int: findType("_Future<int>"),
+ _Future_void: findType("_Future<~>"),
+ _IdentityHashMap_of_nullable_Object_and_nullable_Object: findType("_IdentityHashMap<Object?,Object?>"),
+ _StreamControllerAddStreamState_nullable_Object: findType("_StreamControllerAddStreamState<Object?>"),
+ _SyncCompleter_dynamic: findType("_SyncCompleter<@>"),
+ _ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace: findType("_ZoneFunction<~(Zone,ZoneDelegate,Zone,Object,StackTrace)>"),
+ bool: findType("bool"),
+ bool_Function_Frame: findType("bool(Frame)"),
+ bool_Function_Object: findType("bool(Object)"),
+ bool_Function_String: findType("bool(String)"),
+ double: findType("double"),
+ dynamic: findType("@"),
+ dynamic_Function: findType("@()"),
+ dynamic_Function_Object: findType("@(Object)"),
+ dynamic_Function_Object_StackTrace: findType("@(Object,StackTrace)"),
+ dynamic_Function_String: findType("@(String)"),
+ int: findType("int"),
+ legacy_Never: findType("0&*"),
+ legacy_Object: findType("Object*"),
+ nullable_Future_Null: findType("Future<Null>?"),
+ nullable_Gamepad: findType("Gamepad?"),
+ nullable_JavaScriptObject: findType("JavaScriptObject?"),
+ nullable_List_dynamic: findType("List<@>?"),
+ nullable_Map_of_nullable_Object_and_nullable_Object: findType("Map<Object?,Object?>?"),
+ nullable_Object: findType("Object?"),
+ nullable_StackTrace: findType("StackTrace?"),
+ nullable_Zone: findType("Zone?"),
+ nullable_ZoneDelegate: findType("ZoneDelegate?"),
+ nullable_ZoneSpecification: findType("ZoneSpecification?"),
+ nullable__DelayedEvent_dynamic: findType("_DelayedEvent<@>?"),
+ nullable__FutureListener_dynamic_dynamic: findType("_FutureListener<@,@>?"),
+ nullable__LinkedHashSetCell: findType("_LinkedHashSetCell?"),
+ nullable_void_Function: findType("~()?"),
+ num: findType("num"),
+ void: findType("~"),
+ void_Function: findType("~()"),
+ void_Function_$opt_dynamic: findType("~([@])"),
+ void_Function_JavaScriptObject: findType("~(JavaScriptObject)"),
+ void_Function_Object: findType("~(Object)"),
+ void_Function_Object_StackTrace: findType("~(Object,StackTrace)"),
+ void_Function_String_String: findType("~(String,String)"),
+ void_Function_String_dynamic: findType("~(String,@)"),
+ void_Function_Timer: findType("~(Timer)")
+ };
+ })();
+ (function constants() {
+ var makeConstList = hunkHelpers.makeConstList;
+ B.Interceptor_methods = J.Interceptor.prototype;
+ B.JSArray_methods = J.JSArray.prototype;
+ B.JSInt_methods = J.JSInt.prototype;
+ B.JSNumber_methods = J.JSNumber.prototype;
+ B.JSString_methods = J.JSString.prototype;
+ B.JavaScriptFunction_methods = J.JavaScriptFunction.prototype;
+ B.JavaScriptObject_methods = J.JavaScriptObject.prototype;
+ B.NativeUint8List_methods = A.NativeUint8List.prototype;
+ B.PlainJavaScriptObject_methods = J.PlainJavaScriptObject.prototype;
+ B.UnknownJavaScriptObject_methods = J.UnknownJavaScriptObject.prototype;
+ B.AsciiEncoder_127 = new A.AsciiEncoder(127);
+ B.CONSTANT = new A.Instantiation1(A.math__max$closure(), A.findType("Instantiation1<int>"));
+ B.C_AsciiCodec = new A.AsciiCodec();
+ B.C_Base64Encoder = new A.Base64Encoder();
+ B.C_Base64Codec = new A.Base64Codec();
+ B.C_EmptyIterator = new A.EmptyIterator(A.findType("EmptyIterator<0&>"));
+ B.C_JS_CONST = function getTagFallback(o) {
+ var s = Object.prototype.toString.call(o);
+ return s.substring(8, s.length - 1);
+};
+ B.C_JS_CONST0 = function() {
+ var toStringFunction = Object.prototype.toString;
+ function getTag(o) {
+ var s = toStringFunction.call(o);
+ return s.substring(8, s.length - 1);
+ }
+ function getUnknownTag(object, tag) {
+ if (/^HTML[A-Z].*Element$/.test(tag)) {
+ var name = toStringFunction.call(object);
+ if (name == "[object Object]") return null;
+ return "HTMLElement";
+ }
+ }
+ function getUnknownTagGenericBrowser(object, tag) {
+ if (self.HTMLElement && object instanceof HTMLElement) return "HTMLElement";
+ return getUnknownTag(object, tag);
+ }
+ function prototypeForTag(tag) {
+ if (typeof window == "undefined") return null;
+ if (typeof window[tag] == "undefined") return null;
+ var constructor = window[tag];
+ if (typeof constructor != "function") return null;
+ return constructor.prototype;
+ }
+ function discriminator(tag) { return null; }
+ var isBrowser = typeof navigator == "object";
+ return {
+ getTag: getTag,
+ getUnknownTag: isBrowser ? getUnknownTagGenericBrowser : getUnknownTag,
+ prototypeForTag: prototypeForTag,
+ discriminator: discriminator };
+};
+ B.C_JS_CONST6 = function(getTagFallback) {
+ return function(hooks) {
+ if (typeof navigator != "object") return hooks;
+ var ua = navigator.userAgent;
+ if (ua.indexOf("DumpRenderTree") >= 0) return hooks;
+ if (ua.indexOf("Chrome") >= 0) {
+ function confirm(p) {
+ return typeof window == "object" && window[p] && window[p].name == p;
+ }
+ if (confirm("Window") && confirm("HTMLElement")) return hooks;
+ }
+ hooks.getTag = getTagFallback;
+ };
+};
+ B.C_JS_CONST1 = function(hooks) {
+ if (typeof dartExperimentalFixupGetTag != "function") return hooks;
+ hooks.getTag = dartExperimentalFixupGetTag(hooks.getTag);
+};
+ B.C_JS_CONST2 = function(hooks) {
+ var getTag = hooks.getTag;
+ var prototypeForTag = hooks.prototypeForTag;
+ function getTagFixed(o) {
+ var tag = getTag(o);
+ if (tag == "Document") {
+ if (!!o.xmlVersion) return "!Document";
+ return "!HTMLDocument";
+ }
+ return tag;
+ }
+ function prototypeForTagFixed(tag) {
+ if (tag == "Document") return null;
+ return prototypeForTag(tag);
+ }
+ hooks.getTag = getTagFixed;
+ hooks.prototypeForTag = prototypeForTagFixed;
+};
+ B.C_JS_CONST5 = function(hooks) {
+ var userAgent = typeof navigator == "object" ? navigator.userAgent : "";
+ if (userAgent.indexOf("Firefox") == -1) return hooks;
+ var getTag = hooks.getTag;
+ var quickMap = {
+ "BeforeUnloadEvent": "Event",
+ "DataTransfer": "Clipboard",
+ "GeoGeolocation": "Geolocation",
+ "Location": "!Location",
+ "WorkerMessageEvent": "MessageEvent",
+ "XMLDocument": "!Document"};
+ function getTagFirefox(o) {
+ var tag = getTag(o);
+ return quickMap[tag] || tag;
+ }
+ hooks.getTag = getTagFirefox;
+};
+ B.C_JS_CONST4 = function(hooks) {
+ var userAgent = typeof navigator == "object" ? navigator.userAgent : "";
+ if (userAgent.indexOf("Trident/") == -1) return hooks;
+ var getTag = hooks.getTag;
+ var quickMap = {
+ "BeforeUnloadEvent": "Event",
+ "DataTransfer": "Clipboard",
+ "HTMLDDElement": "HTMLElement",
+ "HTMLDTElement": "HTMLElement",
+ "HTMLPhraseElement": "HTMLElement",
+ "Position": "Geoposition"
+ };
+ function getTagIE(o) {
+ var tag = getTag(o);
+ var newTag = quickMap[tag];
+ if (newTag) return newTag;
+ if (tag == "Object") {
+ if (window.DataView && (o instanceof window.DataView)) return "DataView";
+ }
+ return tag;
+ }
+ function prototypeForTagIE(tag) {
+ var constructor = window[tag];
+ if (constructor == null) return null;
+ return constructor.prototype;
+ }
+ hooks.getTag = getTagIE;
+ hooks.prototypeForTag = prototypeForTagIE;
+};
+ B.C_JS_CONST3 = function(hooks) { return hooks; }
+;
+ B.C_JsonCodec = new A.JsonCodec();
+ B.C_OutOfMemoryError = new A.OutOfMemoryError();
+ B.C_SentinelValue = new A.SentinelValue();
+ B.C_Utf8Codec = new A.Utf8Codec();
+ B.C_Utf8Encoder = new A.Utf8Encoder();
+ B.C__DelayedDone = new A._DelayedDone();
+ B.C__Required = new A._Required();
+ B.C__RootZone = new A._RootZone();
+ B.Duration_0 = new A.Duration(0);
+ B.JsonDecoder_null = new A.JsonDecoder(null);
+ B.JsonEncoder_null = new A.JsonEncoder(null);
+ B.List_M1A = A._setArrayType(makeConstList([0, 0, 24576, 1023, 65534, 34815, 65534, 18431]), type$.JSArray_int);
+ B.List_MMm = A._setArrayType(makeConstList([0, 0, 26624, 1023, 65534, 2047, 65534, 2047]), type$.JSArray_int);
+ B.List_OL3 = A._setArrayType(makeConstList([0, 0, 32722, 12287, 65534, 34815, 65534, 18431]), type$.JSArray_int);
+ B.List_XRg0 = A._setArrayType(makeConstList([0, 0, 32722, 12287, 65535, 34815, 65534, 18431]), type$.JSArray_int);
+ B.List_XRg = A._setArrayType(makeConstList([0, 0, 65490, 12287, 65535, 34815, 65534, 18431]), type$.JSArray_int);
+ B.List_YmH = A._setArrayType(makeConstList([0, 0, 32776, 33792, 1, 10240, 0, 0]), type$.JSArray_int);
+ B.List_ejq = A._setArrayType(makeConstList([0, 0, 32754, 11263, 65534, 34815, 65534, 18431]), type$.JSArray_int);
+ B.List_empty = A._setArrayType(makeConstList([]), type$.JSArray_String);
+ B.List_empty0 = A._setArrayType(makeConstList([]), type$.JSArray_dynamic);
+ B.List_oFp = A._setArrayType(makeConstList([0, 0, 65490, 45055, 65535, 34815, 65534, 18431]), type$.JSArray_int);
+ B.List_yzX = A._setArrayType(makeConstList([0, 0, 27858, 1023, 65534, 51199, 65535, 32767]), type$.JSArray_int);
+ B.Object_empty = {};
+ B.Map_empty = new A.ConstantStringMap(B.Object_empty, [], A.findType("ConstantStringMap<String,String>"));
+ B.Map_empty0 = new A.ConstantStringMap(B.Object_empty, [], A.findType("ConstantStringMap<Symbol0,@>"));
+ B.Symbol_call = new A.Symbol("call");
+ B.Type_ByteBuffer_RkP = A.typeLiteral("ByteBuffer");
+ B.Type_ByteData_zNC = A.typeLiteral("ByteData");
+ B.Type_Float32List_LB7 = A.typeLiteral("Float32List");
+ B.Type_Float64List_LB7 = A.typeLiteral("Float64List");
+ B.Type_Int16List_uXf = A.typeLiteral("Int16List");
+ B.Type_Int32List_O50 = A.typeLiteral("Int32List");
+ B.Type_Int8List_ekJ = A.typeLiteral("Int8List");
+ B.Type_Object_xQ6 = A.typeLiteral("Object");
+ B.Type_Uint16List_2bx = A.typeLiteral("Uint16List");
+ B.Type_Uint32List_2bx = A.typeLiteral("Uint32List");
+ B.Type_Uint8ClampedList_Jik = A.typeLiteral("Uint8ClampedList");
+ B.Type_Uint8List_WLA = A.typeLiteral("Uint8List");
+ B.Utf8Decoder_false = new A.Utf8Decoder(false);
+ B._StringStackTrace_3uE = new A._StringStackTrace("");
+ B._ZoneFunction_3bB = new A._ZoneFunction(B.C__RootZone, A.async___rootCreatePeriodicTimer$closure(), A.findType("_ZoneFunction<Timer(Zone,ZoneDelegate,Zone,Duration,~(Timer))>"));
+ B._ZoneFunction_7G2 = new A._ZoneFunction(B.C__RootZone, A.async___rootRegisterBinaryCallback$closure(), A.findType("_ZoneFunction<0^(1^,2^)(Zone,ZoneDelegate,Zone,0^(1^,2^))<Object?,Object?,Object?>>"));
+ B._ZoneFunction_Eeh = new A._ZoneFunction(B.C__RootZone, A.async___rootRegisterUnaryCallback$closure(), A.findType("_ZoneFunction<0^(1^)(Zone,ZoneDelegate,Zone,0^(1^))<Object?,Object?>>"));
+ B._ZoneFunction_NMc = new A._ZoneFunction(B.C__RootZone, A.async___rootHandleUncaughtError$closure(), type$._ZoneFunction_of_void_Function_Zone_ZoneDelegate_Zone_Object_StackTrace);
+ B._ZoneFunction__RootZone__rootCreateTimer = new A._ZoneFunction(B.C__RootZone, A.async___rootCreateTimer$closure(), A.findType("_ZoneFunction<Timer(Zone,ZoneDelegate,Zone,Duration,~())>"));
+ B._ZoneFunction__RootZone__rootErrorCallback = new A._ZoneFunction(B.C__RootZone, A.async___rootErrorCallback$closure(), A.findType("_ZoneFunction<AsyncError?(Zone,ZoneDelegate,Zone,Object,StackTrace?)>"));
+ B._ZoneFunction__RootZone__rootFork = new A._ZoneFunction(B.C__RootZone, A.async___rootFork$closure(), A.findType("_ZoneFunction<Zone(Zone,ZoneDelegate,Zone,ZoneSpecification?,Map<Object?,Object?>?)>"));
+ B._ZoneFunction__RootZone__rootPrint = new A._ZoneFunction(B.C__RootZone, A.async___rootPrint$closure(), A.findType("_ZoneFunction<~(Zone,ZoneDelegate,Zone,String)>"));
+ B._ZoneFunction__RootZone__rootRegisterCallback = new A._ZoneFunction(B.C__RootZone, A.async___rootRegisterCallback$closure(), A.findType("_ZoneFunction<0^()(Zone,ZoneDelegate,Zone,0^())<Object?>>"));
+ B._ZoneFunction__RootZone__rootRun = new A._ZoneFunction(B.C__RootZone, A.async___rootRun$closure(), A.findType("_ZoneFunction<0^(Zone,ZoneDelegate,Zone,0^())<Object?>>"));
+ B._ZoneFunction__RootZone__rootRunBinary = new A._ZoneFunction(B.C__RootZone, A.async___rootRunBinary$closure(), A.findType("_ZoneFunction<0^(Zone,ZoneDelegate,Zone,0^(1^,2^),1^,2^)<Object?,Object?,Object?>>"));
+ B._ZoneFunction__RootZone__rootRunUnary = new A._ZoneFunction(B.C__RootZone, A.async___rootRunUnary$closure(), A.findType("_ZoneFunction<0^(Zone,ZoneDelegate,Zone,0^(1^),1^)<Object?,Object?>>"));
+ B._ZoneFunction__RootZone__rootScheduleMicrotask = new A._ZoneFunction(B.C__RootZone, A.async___rootScheduleMicrotask$closure(), A.findType("_ZoneFunction<~(Zone,ZoneDelegate,Zone,~())>"));
+ })();
+ (function staticFields() {
+ $._JS_INTEROP_INTERCEPTOR_TAG = null;
+ $.toStringVisiting = A._setArrayType([], type$.JSArray_Object);
+ $.Primitives__identityHashCodeProperty = null;
+ $.BoundClosure__receiverFieldNameCache = null;
+ $.BoundClosure__interceptorFieldNameCache = null;
+ $.getTagFunction = null;
+ $.alternateTagFunction = null;
+ $.prototypeForTagFunction = null;
+ $.dispatchRecordsForInstanceTags = null;
+ $.interceptorsForUncacheableTags = null;
+ $.initNativeDispatchFlag = null;
+ $._nextCallback = null;
+ $._lastCallback = null;
+ $._lastPriorityCallback = null;
+ $._isInCallbackLoop = false;
+ $.Zone__current = B.C__RootZone;
+ $._RootZone__rootDelegate = null;
+ $.Uri__cachedBaseString = "";
+ $.Uri__cachedBaseUri = null;
+ $._currentUriBase = null;
+ $._current = null;
+ $._iframes = A.LinkedHashMap_LinkedHashMap$_empty(type$.int, type$.JavaScriptObject);
+ $._subscriptions = A.LinkedHashMap_LinkedHashMap$_empty(type$.int, A.findType("StreamSubscription<~>"));
+ $._domSubscriptions = A.LinkedHashMap_LinkedHashMap$_empty(type$.int, A.findType("Subscription"));
+ })();
+ (function lazyInitializers() {
+ var _lazyFinal = hunkHelpers.lazyFinal;
+ _lazyFinal($, "DART_CLOSURE_PROPERTY_NAME", "$get$DART_CLOSURE_PROPERTY_NAME", () => A.getIsolateAffinityTag("_$dart_dartClosure"));
+ _lazyFinal($, "nullFuture", "$get$nullFuture", () => B.C__RootZone.run$1$1(new A.nullFuture_closure(), A.findType("Future<Null>")));
+ _lazyFinal($, "TypeErrorDecoder_noSuchMethodPattern", "$get$TypeErrorDecoder_noSuchMethodPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn({
+ toString: function() {
+ return "$receiver$";
+ }
+ })));
+ _lazyFinal($, "TypeErrorDecoder_notClosurePattern", "$get$TypeErrorDecoder_notClosurePattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn({$method$: null,
+ toString: function() {
+ return "$receiver$";
+ }
+ })));
+ _lazyFinal($, "TypeErrorDecoder_nullCallPattern", "$get$TypeErrorDecoder_nullCallPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn(null)));
+ _lazyFinal($, "TypeErrorDecoder_nullLiteralCallPattern", "$get$TypeErrorDecoder_nullLiteralCallPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ var $argumentsExpr$ = "$arguments$";
+ try {
+ null.$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "TypeErrorDecoder_undefinedCallPattern", "$get$TypeErrorDecoder_undefinedCallPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokeCallErrorOn(void 0)));
+ _lazyFinal($, "TypeErrorDecoder_undefinedLiteralCallPattern", "$get$TypeErrorDecoder_undefinedLiteralCallPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ var $argumentsExpr$ = "$arguments$";
+ try {
+ (void 0).$method$($argumentsExpr$);
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "TypeErrorDecoder_nullPropertyPattern", "$get$TypeErrorDecoder_nullPropertyPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokePropertyErrorOn(null)));
+ _lazyFinal($, "TypeErrorDecoder_nullLiteralPropertyPattern", "$get$TypeErrorDecoder_nullLiteralPropertyPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ try {
+ null.$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "TypeErrorDecoder_undefinedPropertyPattern", "$get$TypeErrorDecoder_undefinedPropertyPattern", () => A.TypeErrorDecoder_extractPattern(A.TypeErrorDecoder_provokePropertyErrorOn(void 0)));
+ _lazyFinal($, "TypeErrorDecoder_undefinedLiteralPropertyPattern", "$get$TypeErrorDecoder_undefinedLiteralPropertyPattern", () => A.TypeErrorDecoder_extractPattern(function() {
+ try {
+ (void 0).$method$;
+ } catch (e) {
+ return e.message;
+ }
+ }()));
+ _lazyFinal($, "_AsyncRun__scheduleImmediateClosure", "$get$_AsyncRun__scheduleImmediateClosure", () => A._AsyncRun__initializeScheduleImmediate());
+ _lazyFinal($, "Future__nullFuture", "$get$Future__nullFuture", () => A.findType("_Future<Null>")._as($.$get$nullFuture()));
+ _lazyFinal($, "_RootZone__rootMap", "$get$_RootZone__rootMap", () => {
+ var t1 = type$.dynamic;
+ return A.HashMap_HashMap(t1, t1);
+ });
+ _lazyFinal($, "Utf8Decoder__decoder", "$get$Utf8Decoder__decoder", () => new A.Utf8Decoder__decoder_closure().call$0());
+ _lazyFinal($, "Utf8Decoder__decoderNonfatal", "$get$Utf8Decoder__decoderNonfatal", () => new A.Utf8Decoder__decoderNonfatal_closure().call$0());
+ _lazyFinal($, "_Base64Decoder__inverseAlphabet", "$get$_Base64Decoder__inverseAlphabet", () => A.NativeInt8List__create1(A._ensureNativeList(A._setArrayType([-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -2, -2, -2, -2, -2, 62, -2, 62, -2, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -1, -2, -2, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, 63, -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2], type$.JSArray_int))));
+ _lazyFinal($, "_Uri__isWindowsCached", "$get$_Uri__isWindowsCached", () => typeof process != "undefined" && Object.prototype.toString.call(process) == "[object process]" && process.platform == "win32");
+ _lazyFinal($, "_Uri__needsNoEncoding", "$get$_Uri__needsNoEncoding", () => A.RegExp_RegExp("^[\\-\\.0-9A-Z_a-z~]*$", false));
+ _lazyFinal($, "_hashSeed", "$get$_hashSeed", () => A.objectHashCode(B.Type_Object_xQ6));
+ _lazyFinal($, "_scannerTables", "$get$_scannerTables", () => A._createTables());
+ _lazyFinal($, "windows", "$get$windows", () => A.Context_Context($.$get$Style_windows()));
+ _lazyFinal($, "context", "$get$context", () => new A.Context($.$get$Style_platform(), null));
+ _lazyFinal($, "Style_posix", "$get$Style_posix", () => new A.PosixStyle(A.RegExp_RegExp("/", false), A.RegExp_RegExp("[^/]$", false), A.RegExp_RegExp("^/", false)));
+ _lazyFinal($, "Style_windows", "$get$Style_windows", () => new A.WindowsStyle(A.RegExp_RegExp("[/\\\\]", false), A.RegExp_RegExp("[^/\\\\]$", false), A.RegExp_RegExp("^(\\\\\\\\[^\\\\]+\\\\[^\\\\/]+|[a-zA-Z]:[/\\\\])", false), A.RegExp_RegExp("^[/\\\\](?![/\\\\])", false)));
+ _lazyFinal($, "Style_url", "$get$Style_url", () => new A.UrlStyle(A.RegExp_RegExp("/", false), A.RegExp_RegExp("(^[a-zA-Z][-+.a-zA-Z\\d]*://|[^/])$", false), A.RegExp_RegExp("[a-zA-Z][-+.a-zA-Z\\d]*://[^/]*", false), A.RegExp_RegExp("^/", false)));
+ _lazyFinal($, "Style_platform", "$get$Style_platform", () => A.Style__getPlatformStyle());
+ _lazyFinal($, "_vmFrame", "$get$_vmFrame", () => A.RegExp_RegExp("^#\\d+\\s+(\\S.*) \\((.+?)((?::\\d+){0,2})\\)$", false));
+ _lazyFinal($, "_v8Frame", "$get$_v8Frame", () => A.RegExp_RegExp("^\\s*at (?:(\\S.*?)(?: \\[as [^\\]]+\\])? \\((.*)\\)|(.*))$", false));
+ _lazyFinal($, "_v8UrlLocation", "$get$_v8UrlLocation", () => A.RegExp_RegExp("^(.*?):(\\d+)(?::(\\d+))?$|native$", false));
+ _lazyFinal($, "_v8EvalLocation", "$get$_v8EvalLocation", () => A.RegExp_RegExp("^eval at (?:\\S.*?) \\((.*)\\)(?:, .*?:\\d+:\\d+)?$", false));
+ _lazyFinal($, "_firefoxEvalLocation", "$get$_firefoxEvalLocation", () => A.RegExp_RegExp("(\\S+)@(\\S+) line (\\d+) >.* (Function|eval):\\d+:\\d+", false));
+ _lazyFinal($, "_firefoxSafariFrame", "$get$_firefoxSafariFrame", () => A.RegExp_RegExp("^(?:([^@(/]*)(?:\\(.*\\))?((?:/[^/]*)*)(?:\\(.*\\))?@)?(.*?):(\\d*)(?::(\\d*))?$", false));
+ _lazyFinal($, "_friendlyFrame", "$get$_friendlyFrame", () => A.RegExp_RegExp("^(\\S+)(?: (\\d+)(?::(\\d+))?)?\\s+([^\\d].*)$", false));
+ _lazyFinal($, "_asyncBody", "$get$_asyncBody", () => A.RegExp_RegExp("<(<anonymous closure>|[^>]+)_async_body>", false));
+ _lazyFinal($, "_initialDot", "$get$_initialDot", () => A.RegExp_RegExp("^\\.", false));
+ _lazyFinal($, "Frame__uriRegExp", "$get$Frame__uriRegExp", () => A.RegExp_RegExp("^[a-zA-Z][-+.a-zA-Z\\d]*://", false));
+ _lazyFinal($, "Frame__windowsRegExp", "$get$Frame__windowsRegExp", () => A.RegExp_RegExp("^([a-zA-Z]:[\\\\/]|\\\\\\\\)", false));
+ _lazyFinal($, "_terseRegExp", "$get$_terseRegExp", () => A.RegExp_RegExp("(-patch)?([/\\\\].*)?$", false));
+ _lazyFinal($, "_v8Trace", "$get$_v8Trace", () => A.RegExp_RegExp("\\n ?at ", false));
+ _lazyFinal($, "_v8TraceLine", "$get$_v8TraceLine", () => A.RegExp_RegExp(" ?at ", false));
+ _lazyFinal($, "_firefoxEvalTrace", "$get$_firefoxEvalTrace", () => A.RegExp_RegExp("@\\S+ line \\d+ >.* (Function|eval):\\d+:\\d+", false));
+ _lazyFinal($, "_firefoxSafariTrace", "$get$_firefoxSafariTrace", () => A.RegExp_RegExp("^(([.0-9A-Za-z_$/<]|\\(.*\\))*@)?[^\\s]*:\\d*$", true));
+ _lazyFinal($, "_friendlyTrace", "$get$_friendlyTrace", () => A.RegExp_RegExp("^[^\\s<][^\\s]*( \\d+(:\\d+)?)?[ \\t]+[^\\s]+$", true));
+ _lazyFinal($, "vmChainGap", "$get$vmChainGap", () => A.RegExp_RegExp("^<asynchronous suspension>\\n?$", true));
+ _lazyFinal($, "_currentUrl", "$get$_currentUrl", () => A.Uri_parse(A.getProperty(A.getProperty(self.window, "location", type$.JavaScriptObject), "href", type$.String)));
+ })();
+ (function nativeSupport() {
+ !function() {
+ var intern = function(s) {
+ var o = {};
+ o[s] = 1;
+ return Object.keys(hunkHelpers.convertToFastObject(o))[0];
+ };
+ init.getIsolateTag = function(name) {
+ return intern("___dart_" + name + init.isolateTag);
+ };
+ var tableProperty = "___dart_isolate_tags_";
+ var usedProperties = Object[tableProperty] || (Object[tableProperty] = Object.create(null));
+ var rootProperty = "_ZxYxX";
+ for (var i = 0;; i++) {
+ var property = intern(rootProperty + "_" + i + "_");
+ if (!(property in usedProperties)) {
+ usedProperties[property] = 1;
+ init.isolateTag = property;
+ break;
+ }
+ }
+ init.dispatchPropertyName = init.getIsolateTag("dispatch_record");
+ }();
+ hunkHelpers.setOrUpdateInterceptorsByTag({WebGL: J.Interceptor, AbortPaymentEvent: J.JavaScriptObject, AnimationEffectReadOnly: J.JavaScriptObject, AnimationEffectTiming: J.JavaScriptObject, AnimationEffectTimingReadOnly: J.JavaScriptObject, AnimationEvent: J.JavaScriptObject, AnimationPlaybackEvent: J.JavaScriptObject, AnimationTimeline: J.JavaScriptObject, AnimationWorkletGlobalScope: J.JavaScriptObject, ApplicationCacheErrorEvent: J.JavaScriptObject, AuthenticatorAssertionResponse: J.JavaScriptObject, AuthenticatorAttestationResponse: J.JavaScriptObject, AuthenticatorResponse: J.JavaScriptObject, BackgroundFetchClickEvent: J.JavaScriptObject, BackgroundFetchEvent: J.JavaScriptObject, BackgroundFetchFailEvent: J.JavaScriptObject, BackgroundFetchFetch: J.JavaScriptObject, BackgroundFetchManager: J.JavaScriptObject, BackgroundFetchSettledFetch: J.JavaScriptObject, BackgroundFetchedEvent: J.JavaScriptObject, BarProp: J.JavaScriptObject, BarcodeDetector: J.JavaScriptObject, BeforeInstallPromptEvent: J.JavaScriptObject, BeforeUnloadEvent: J.JavaScriptObject, BlobEvent: J.JavaScriptObject, BluetoothRemoteGATTDescriptor: J.JavaScriptObject, Body: J.JavaScriptObject, BudgetState: J.JavaScriptObject, CacheStorage: J.JavaScriptObject, CanMakePaymentEvent: J.JavaScriptObject, CanvasGradient: J.JavaScriptObject, CanvasPattern: J.JavaScriptObject, CanvasRenderingContext2D: J.JavaScriptObject, Client: J.JavaScriptObject, Clients: J.JavaScriptObject, ClipboardEvent: J.JavaScriptObject, CloseEvent: J.JavaScriptObject, CompositionEvent: J.JavaScriptObject, CookieStore: J.JavaScriptObject, Coordinates: J.JavaScriptObject, Credential: J.JavaScriptObject, CredentialUserData: J.JavaScriptObject, CredentialsContainer: J.JavaScriptObject, Crypto: J.JavaScriptObject, CryptoKey: J.JavaScriptObject, CSS: J.JavaScriptObject, CSSVariableReferenceValue: J.JavaScriptObject, CustomElementRegistry: J.JavaScriptObject, CustomEvent: J.JavaScriptObject, DataTransfer: J.JavaScriptObject, DataTransferItem: J.JavaScriptObject, DeprecatedStorageInfo: J.JavaScriptObject, DeprecatedStorageQuota: J.JavaScriptObject, DeprecationReport: J.JavaScriptObject, DetectedBarcode: J.JavaScriptObject, DetectedFace: J.JavaScriptObject, DetectedText: J.JavaScriptObject, DeviceAcceleration: J.JavaScriptObject, DeviceMotionEvent: J.JavaScriptObject, DeviceOrientationEvent: J.JavaScriptObject, DeviceRotationRate: J.JavaScriptObject, DirectoryEntry: J.JavaScriptObject, webkitFileSystemDirectoryEntry: J.JavaScriptObject, FileSystemDirectoryEntry: J.JavaScriptObject, DirectoryReader: J.JavaScriptObject, WebKitDirectoryReader: J.JavaScriptObject, webkitFileSystemDirectoryReader: J.JavaScriptObject, FileSystemDirectoryReader: J.JavaScriptObject, DocumentOrShadowRoot: J.JavaScriptObject, DocumentTimeline: J.JavaScriptObject, DOMError: J.JavaScriptObject, DOMImplementation: J.JavaScriptObject, Iterator: J.JavaScriptObject, DOMMatrix: J.JavaScriptObject, DOMMatrixReadOnly: J.JavaScriptObject, DOMParser: J.JavaScriptObject, DOMPoint: J.JavaScriptObject, DOMPointReadOnly: J.JavaScriptObject, DOMQuad: J.JavaScriptObject, DOMStringMap: J.JavaScriptObject, Entry: J.JavaScriptObject, webkitFileSystemEntry: J.JavaScriptObject, FileSystemEntry: J.JavaScriptObject, ErrorEvent: J.JavaScriptObject, Event: J.JavaScriptObject, InputEvent: J.JavaScriptObject, SubmitEvent: J.JavaScriptObject, ExtendableEvent: J.JavaScriptObject, ExtendableMessageEvent: J.JavaScriptObject, External: J.JavaScriptObject, FaceDetector: J.JavaScriptObject, FederatedCredential: J.JavaScriptObject, FetchEvent: J.JavaScriptObject, FileEntry: J.JavaScriptObject, webkitFileSystemFileEntry: J.JavaScriptObject, FileSystemFileEntry: J.JavaScriptObject, DOMFileSystem: J.JavaScriptObject, WebKitFileSystem: J.JavaScriptObject, webkitFileSystem: J.JavaScriptObject, FileSystem: J.JavaScriptObject, FocusEvent: J.JavaScriptObject, FontFace: J.JavaScriptObject, FontFaceSetLoadEvent: J.JavaScriptObject, FontFaceSource: J.JavaScriptObject, ForeignFetchEvent: J.JavaScriptObject, FormData: J.JavaScriptObject, GamepadButton: J.JavaScriptObject, GamepadEvent: J.JavaScriptObject, GamepadPose: J.JavaScriptObject, Geolocation: J.JavaScriptObject, Position: J.JavaScriptObject, GeolocationPosition: J.JavaScriptObject, HashChangeEvent: J.JavaScriptObject, Headers: J.JavaScriptObject, HTMLHyperlinkElementUtils: J.JavaScriptObject, IdleDeadline: J.JavaScriptObject, ImageBitmap: J.JavaScriptObject, ImageBitmapRenderingContext: J.JavaScriptObject, ImageCapture: J.JavaScriptObject, ImageData: J.JavaScriptObject, InputDeviceCapabilities: J.JavaScriptObject, InstallEvent: J.JavaScriptObject, IntersectionObserver: J.JavaScriptObject, IntersectionObserverEntry: J.JavaScriptObject, InterventionReport: J.JavaScriptObject, KeyboardEvent: J.JavaScriptObject, KeyframeEffect: J.JavaScriptObject, KeyframeEffectReadOnly: J.JavaScriptObject, MediaCapabilities: J.JavaScriptObject, MediaCapabilitiesInfo: J.JavaScriptObject, MediaDeviceInfo: J.JavaScriptObject, MediaEncryptedEvent: J.JavaScriptObject, MediaError: J.JavaScriptObject, MediaKeyMessageEvent: J.JavaScriptObject, MediaKeyStatusMap: J.JavaScriptObject, MediaKeySystemAccess: J.JavaScriptObject, MediaKeys: J.JavaScriptObject, MediaKeysPolicy: J.JavaScriptObject, MediaMetadata: J.JavaScriptObject, MediaQueryListEvent: J.JavaScriptObject, MediaSession: J.JavaScriptObject, MediaSettingsRange: J.JavaScriptObject, MediaStreamEvent: J.JavaScriptObject, MediaStreamTrackEvent: J.JavaScriptObject, MemoryInfo: J.JavaScriptObject, MessageChannel: J.JavaScriptObject, MessageEvent: J.JavaScriptObject, Metadata: J.JavaScriptObject, MIDIConnectionEvent: J.JavaScriptObject, MIDIMessageEvent: J.JavaScriptObject, MouseEvent: J.JavaScriptObject, DragEvent: J.JavaScriptObject, MutationEvent: J.JavaScriptObject, MutationObserver: J.JavaScriptObject, WebKitMutationObserver: J.JavaScriptObject, MutationRecord: J.JavaScriptObject, NavigationPreloadManager: J.JavaScriptObject, Navigator: J.JavaScriptObject, NavigatorAutomationInformation: J.JavaScriptObject, NavigatorConcurrentHardware: J.JavaScriptObject, NavigatorCookies: J.JavaScriptObject, NavigatorUserMediaError: J.JavaScriptObject, NodeFilter: J.JavaScriptObject, NodeIterator: J.JavaScriptObject, NonDocumentTypeChildNode: J.JavaScriptObject, NonElementParentNode: J.JavaScriptObject, NoncedElement: J.JavaScriptObject, NotificationEvent: J.JavaScriptObject, OffscreenCanvasRenderingContext2D: J.JavaScriptObject, OverconstrainedError: J.JavaScriptObject, PageTransitionEvent: J.JavaScriptObject, PaintRenderingContext2D: J.JavaScriptObject, PaintSize: J.JavaScriptObject, PaintWorkletGlobalScope: J.JavaScriptObject, PasswordCredential: J.JavaScriptObject, Path2D: J.JavaScriptObject, PaymentAddress: J.JavaScriptObject, PaymentInstruments: J.JavaScriptObject, PaymentManager: J.JavaScriptObject, PaymentRequestEvent: J.JavaScriptObject, PaymentRequestUpdateEvent: J.JavaScriptObject, PaymentResponse: J.JavaScriptObject, PerformanceEntry: J.JavaScriptObject, PerformanceLongTaskTiming: J.JavaScriptObject, PerformanceMark: J.JavaScriptObject, PerformanceMeasure: J.JavaScriptObject, PerformanceNavigation: J.JavaScriptObject, PerformanceNavigationTiming: J.JavaScriptObject, PerformanceObserver: J.JavaScriptObject, PerformanceObserverEntryList: J.JavaScriptObject, PerformancePaintTiming: J.JavaScriptObject, PerformanceResourceTiming: J.JavaScriptObject, PerformanceServerTiming: J.JavaScriptObject, PerformanceTiming: J.JavaScriptObject, Permissions: J.JavaScriptObject, PhotoCapabilities: J.JavaScriptObject, PointerEvent: J.JavaScriptObject, PopStateEvent: J.JavaScriptObject, PositionError: J.JavaScriptObject, GeolocationPositionError: J.JavaScriptObject, Presentation: J.JavaScriptObject, PresentationConnectionAvailableEvent: J.JavaScriptObject, PresentationConnectionCloseEvent: J.JavaScriptObject, PresentationReceiver: J.JavaScriptObject, ProgressEvent: J.JavaScriptObject, PromiseRejectionEvent: J.JavaScriptObject, PublicKeyCredential: J.JavaScriptObject, PushEvent: J.JavaScriptObject, PushManager: J.JavaScriptObject, PushMessageData: J.JavaScriptObject, PushSubscription: J.JavaScriptObject, PushSubscriptionOptions: J.JavaScriptObject, Range: J.JavaScriptObject, RelatedApplication: J.JavaScriptObject, ReportBody: J.JavaScriptObject, ReportingObserver: J.JavaScriptObject, ResizeObserver: J.JavaScriptObject, ResizeObserverEntry: J.JavaScriptObject, RTCCertificate: J.JavaScriptObject, RTCDataChannelEvent: J.JavaScriptObject, RTCDTMFToneChangeEvent: J.JavaScriptObject, RTCIceCandidate: J.JavaScriptObject, mozRTCIceCandidate: J.JavaScriptObject, RTCLegacyStatsReport: J.JavaScriptObject, RTCPeerConnectionIceEvent: J.JavaScriptObject, RTCRtpContributingSource: J.JavaScriptObject, RTCRtpReceiver: J.JavaScriptObject, RTCRtpSender: J.JavaScriptObject, RTCSessionDescription: J.JavaScriptObject, mozRTCSessionDescription: J.JavaScriptObject, RTCStatsResponse: J.JavaScriptObject, RTCTrackEvent: J.JavaScriptObject, Screen: J.JavaScriptObject, ScrollState: J.JavaScriptObject, ScrollTimeline: J.JavaScriptObject, SecurityPolicyViolationEvent: J.JavaScriptObject, Selection: J.JavaScriptObject, SensorErrorEvent: J.JavaScriptObject, SharedArrayBuffer: J.JavaScriptObject, SpeechRecognitionAlternative: J.JavaScriptObject, SpeechRecognitionError: J.JavaScriptObject, SpeechRecognitionEvent: J.JavaScriptObject, SpeechSynthesisEvent: J.JavaScriptObject, SpeechSynthesisVoice: J.JavaScriptObject, StaticRange: J.JavaScriptObject, StorageEvent: J.JavaScriptObject, StorageManager: J.JavaScriptObject, StyleMedia: J.JavaScriptObject, StylePropertyMap: J.JavaScriptObject, StylePropertyMapReadonly: J.JavaScriptObject, SyncEvent: J.JavaScriptObject, SyncManager: J.JavaScriptObject, TaskAttributionTiming: J.JavaScriptObject, TextDetector: J.JavaScriptObject, TextEvent: J.JavaScriptObject, TextMetrics: J.JavaScriptObject, TouchEvent: J.JavaScriptObject, TrackDefault: J.JavaScriptObject, TrackEvent: J.JavaScriptObject, TransitionEvent: J.JavaScriptObject, WebKitTransitionEvent: J.JavaScriptObject, TreeWalker: J.JavaScriptObject, TrustedHTML: J.JavaScriptObject, TrustedScriptURL: J.JavaScriptObject, TrustedURL: J.JavaScriptObject, UIEvent: J.JavaScriptObject, UnderlyingSourceBase: J.JavaScriptObject, URLSearchParams: J.JavaScriptObject, VRCoordinateSystem: J.JavaScriptObject, VRDeviceEvent: J.JavaScriptObject, VRDisplayCapabilities: J.JavaScriptObject, VRDisplayEvent: J.JavaScriptObject, VREyeParameters: J.JavaScriptObject, VRFrameData: J.JavaScriptObject, VRFrameOfReference: J.JavaScriptObject, VRPose: J.JavaScriptObject, VRSessionEvent: J.JavaScriptObject, VRStageBounds: J.JavaScriptObject, VRStageBoundsPoint: J.JavaScriptObject, VRStageParameters: J.JavaScriptObject, ValidityState: J.JavaScriptObject, VideoPlaybackQuality: J.JavaScriptObject, VideoTrack: J.JavaScriptObject, VTTRegion: J.JavaScriptObject, WheelEvent: J.JavaScriptObject, WindowClient: J.JavaScriptObject, WorkletAnimation: J.JavaScriptObject, WorkletGlobalScope: J.JavaScriptObject, XPathEvaluator: J.JavaScriptObject, XPathExpression: J.JavaScriptObject, XPathNSResolver: J.JavaScriptObject, XPathResult: J.JavaScriptObject, XMLSerializer: J.JavaScriptObject, XSLTProcessor: J.JavaScriptObject, Bluetooth: J.JavaScriptObject, BluetoothCharacteristicProperties: J.JavaScriptObject, BluetoothRemoteGATTServer: J.JavaScriptObject, BluetoothRemoteGATTService: J.JavaScriptObject, BluetoothUUID: J.JavaScriptObject, BudgetService: J.JavaScriptObject, Cache: J.JavaScriptObject, DOMFileSystemSync: J.JavaScriptObject, DirectoryEntrySync: J.JavaScriptObject, DirectoryReaderSync: J.JavaScriptObject, EntrySync: J.JavaScriptObject, FileEntrySync: J.JavaScriptObject, FileReaderSync: J.JavaScriptObject, FileWriterSync: J.JavaScriptObject, HTMLAllCollection: J.JavaScriptObject, Mojo: J.JavaScriptObject, MojoHandle: J.JavaScriptObject, MojoInterfaceRequestEvent: J.JavaScriptObject, MojoWatcher: J.JavaScriptObject, NFC: J.JavaScriptObject, PagePopupController: J.JavaScriptObject, Report: J.JavaScriptObject, Request: J.JavaScriptObject, ResourceProgressEvent: J.JavaScriptObject, Response: J.JavaScriptObject, SubtleCrypto: J.JavaScriptObject, USBAlternateInterface: J.JavaScriptObject, USBConfiguration: J.JavaScriptObject, USBConnectionEvent: J.JavaScriptObject, USBDevice: J.JavaScriptObject, USBEndpoint: J.JavaScriptObject, USBInTransferResult: J.JavaScriptObject, USBInterface: J.JavaScriptObject, USBIsochronousInTransferPacket: J.JavaScriptObject, USBIsochronousInTransferResult: J.JavaScriptObject, USBIsochronousOutTransferPacket: J.JavaScriptObject, USBIsochronousOutTransferResult: J.JavaScriptObject, USBOutTransferResult: J.JavaScriptObject, WorkerLocation: J.JavaScriptObject, WorkerNavigator: J.JavaScriptObject, Worklet: J.JavaScriptObject, IDBCursor: J.JavaScriptObject, IDBCursorWithValue: J.JavaScriptObject, IDBFactory: J.JavaScriptObject, IDBIndex: J.JavaScriptObject, IDBKeyRange: J.JavaScriptObject, IDBObjectStore: J.JavaScriptObject, IDBObservation: J.JavaScriptObject, IDBObserver: J.JavaScriptObject, IDBObserverChanges: J.JavaScriptObject, IDBVersionChangeEvent: J.JavaScriptObject, SVGAngle: J.JavaScriptObject, SVGAnimatedAngle: J.JavaScriptObject, SVGAnimatedBoolean: J.JavaScriptObject, SVGAnimatedEnumeration: J.JavaScriptObject, SVGAnimatedInteger: J.JavaScriptObject, SVGAnimatedLength: J.JavaScriptObject, SVGAnimatedLengthList: J.JavaScriptObject, SVGAnimatedNumber: J.JavaScriptObject, SVGAnimatedNumberList: J.JavaScriptObject, SVGAnimatedPreserveAspectRatio: J.JavaScriptObject, SVGAnimatedRect: J.JavaScriptObject, SVGAnimatedString: J.JavaScriptObject, SVGAnimatedTransformList: J.JavaScriptObject, SVGMatrix: J.JavaScriptObject, SVGPoint: J.JavaScriptObject, SVGPreserveAspectRatio: J.JavaScriptObject, SVGRect: J.JavaScriptObject, SVGUnitTypes: J.JavaScriptObject, AudioListener: J.JavaScriptObject, AudioParam: J.JavaScriptObject, AudioProcessingEvent: J.JavaScriptObject, AudioTrack: J.JavaScriptObject, AudioWorkletGlobalScope: J.JavaScriptObject, AudioWorkletProcessor: J.JavaScriptObject, OfflineAudioCompletionEvent: J.JavaScriptObject, PeriodicWave: J.JavaScriptObject, WebGLActiveInfo: J.JavaScriptObject, ANGLEInstancedArrays: J.JavaScriptObject, ANGLE_instanced_arrays: J.JavaScriptObject, WebGLBuffer: J.JavaScriptObject, WebGLCanvas: J.JavaScriptObject, WebGLColorBufferFloat: J.JavaScriptObject, WebGLCompressedTextureASTC: J.JavaScriptObject, WebGLCompressedTextureATC: J.JavaScriptObject, WEBGL_compressed_texture_atc: J.JavaScriptObject, WebGLCompressedTextureETC1: J.JavaScriptObject, WEBGL_compressed_texture_etc1: J.JavaScriptObject, WebGLCompressedTextureETC: J.JavaScriptObject, WebGLCompressedTexturePVRTC: J.JavaScriptObject, WEBGL_compressed_texture_pvrtc: J.JavaScriptObject, WebGLCompressedTextureS3TC: J.JavaScriptObject, WEBGL_compressed_texture_s3tc: J.JavaScriptObject, WebGLCompressedTextureS3TCsRGB: J.JavaScriptObject, WebGLContextEvent: J.JavaScriptObject, WebGLDebugRendererInfo: J.JavaScriptObject, WEBGL_debug_renderer_info: J.JavaScriptObject, WebGLDebugShaders: J.JavaScriptObject, WEBGL_debug_shaders: J.JavaScriptObject, WebGLDepthTexture: J.JavaScriptObject, WEBGL_depth_texture: J.JavaScriptObject, WebGLDrawBuffers: J.JavaScriptObject, WEBGL_draw_buffers: J.JavaScriptObject, EXTsRGB: J.JavaScriptObject, EXT_sRGB: J.JavaScriptObject, EXTBlendMinMax: J.JavaScriptObject, EXT_blend_minmax: J.JavaScriptObject, EXTColorBufferFloat: J.JavaScriptObject, EXTColorBufferHalfFloat: J.JavaScriptObject, EXTDisjointTimerQuery: J.JavaScriptObject, EXTDisjointTimerQueryWebGL2: J.JavaScriptObject, EXTFragDepth: J.JavaScriptObject, EXT_frag_depth: J.JavaScriptObject, EXTShaderTextureLOD: J.JavaScriptObject, EXT_shader_texture_lod: J.JavaScriptObject, EXTTextureFilterAnisotropic: J.JavaScriptObject, EXT_texture_filter_anisotropic: J.JavaScriptObject, WebGLFramebuffer: J.JavaScriptObject, WebGLGetBufferSubDataAsync: J.JavaScriptObject, WebGLLoseContext: J.JavaScriptObject, WebGLExtensionLoseContext: J.JavaScriptObject, WEBGL_lose_context: J.JavaScriptObject, OESElementIndexUint: J.JavaScriptObject, OES_element_index_uint: J.JavaScriptObject, OESStandardDerivatives: J.JavaScriptObject, OES_standard_derivatives: J.JavaScriptObject, OESTextureFloat: J.JavaScriptObject, OES_texture_float: J.JavaScriptObject, OESTextureFloatLinear: J.JavaScriptObject, OES_texture_float_linear: J.JavaScriptObject, OESTextureHalfFloat: J.JavaScriptObject, OES_texture_half_float: J.JavaScriptObject, OESTextureHalfFloatLinear: J.JavaScriptObject, OES_texture_half_float_linear: J.JavaScriptObject, OESVertexArrayObject: J.JavaScriptObject, OES_vertex_array_object: J.JavaScriptObject, WebGLProgram: J.JavaScriptObject, WebGLQuery: J.JavaScriptObject, WebGLRenderbuffer: J.JavaScriptObject, WebGLRenderingContext: J.JavaScriptObject, WebGL2RenderingContext: J.JavaScriptObject, WebGLSampler: J.JavaScriptObject, WebGLShader: J.JavaScriptObject, WebGLShaderPrecisionFormat: J.JavaScriptObject, WebGLSync: J.JavaScriptObject, WebGLTexture: J.JavaScriptObject, WebGLTimerQueryEXT: J.JavaScriptObject, WebGLTransformFeedback: J.JavaScriptObject, WebGLUniformLocation: J.JavaScriptObject, WebGLVertexArrayObject: J.JavaScriptObject, WebGLVertexArrayObjectOES: J.JavaScriptObject, WebGL2RenderingContextBase: J.JavaScriptObject, ArrayBuffer: A.NativeByteBuffer, ArrayBufferView: A.NativeTypedData, DataView: A.NativeByteData, Float32Array: A.NativeFloat32List, Float64Array: A.NativeFloat64List, Int16Array: A.NativeInt16List, Int32Array: A.NativeInt32List, Int8Array: A.NativeInt8List, Uint16Array: A.NativeUint16List, Uint32Array: A.NativeUint32List, Uint8ClampedArray: A.NativeUint8ClampedList, CanvasPixelArray: A.NativeUint8ClampedList, Uint8Array: A.NativeUint8List, HTMLAudioElement: A.HtmlElement, HTMLBRElement: A.HtmlElement, HTMLBaseElement: A.HtmlElement, HTMLBodyElement: A.HtmlElement, HTMLButtonElement: A.HtmlElement, HTMLCanvasElement: A.HtmlElement, HTMLContentElement: A.HtmlElement, HTMLDListElement: A.HtmlElement, HTMLDataElement: A.HtmlElement, HTMLDataListElement: A.HtmlElement, HTMLDetailsElement: A.HtmlElement, HTMLDialogElement: A.HtmlElement, HTMLDivElement: A.HtmlElement, HTMLEmbedElement: A.HtmlElement, HTMLFieldSetElement: A.HtmlElement, HTMLHRElement: A.HtmlElement, HTMLHeadElement: A.HtmlElement, HTMLHeadingElement: A.HtmlElement, HTMLHtmlElement: A.HtmlElement, HTMLIFrameElement: A.HtmlElement, HTMLImageElement: A.HtmlElement, HTMLInputElement: A.HtmlElement, HTMLLIElement: A.HtmlElement, HTMLLabelElement: A.HtmlElement, HTMLLegendElement: A.HtmlElement, HTMLLinkElement: A.HtmlElement, HTMLMapElement: A.HtmlElement, HTMLMediaElement: A.HtmlElement, HTMLMenuElement: A.HtmlElement, HTMLMetaElement: A.HtmlElement, HTMLMeterElement: A.HtmlElement, HTMLModElement: A.HtmlElement, HTMLOListElement: A.HtmlElement, HTMLObjectElement: A.HtmlElement, HTMLOptGroupElement: A.HtmlElement, HTMLOptionElement: A.HtmlElement, HTMLOutputElement: A.HtmlElement, HTMLParagraphElement: A.HtmlElement, HTMLParamElement: A.HtmlElement, HTMLPictureElement: A.HtmlElement, HTMLPreElement: A.HtmlElement, HTMLProgressElement: A.HtmlElement, HTMLQuoteElement: A.HtmlElement, HTMLScriptElement: A.HtmlElement, HTMLShadowElement: A.HtmlElement, HTMLSlotElement: A.HtmlElement, HTMLSourceElement: A.HtmlElement, HTMLSpanElement: A.HtmlElement, HTMLStyleElement: A.HtmlElement, HTMLTableCaptionElement: A.HtmlElement, HTMLTableCellElement: A.HtmlElement, HTMLTableDataCellElement: A.HtmlElement, HTMLTableHeaderCellElement: A.HtmlElement, HTMLTableColElement: A.HtmlElement, HTMLTableElement: A.HtmlElement, HTMLTableRowElement: A.HtmlElement, HTMLTableSectionElement: A.HtmlElement, HTMLTemplateElement: A.HtmlElement, HTMLTextAreaElement: A.HtmlElement, HTMLTimeElement: A.HtmlElement, HTMLTitleElement: A.HtmlElement, HTMLTrackElement: A.HtmlElement, HTMLUListElement: A.HtmlElement, HTMLUnknownElement: A.HtmlElement, HTMLVideoElement: A.HtmlElement, HTMLDirectoryElement: A.HtmlElement, HTMLFontElement: A.HtmlElement, HTMLFrameElement: A.HtmlElement, HTMLFrameSetElement: A.HtmlElement, HTMLMarqueeElement: A.HtmlElement, HTMLElement: A.HtmlElement, AccessibleNodeList: A.AccessibleNodeList, HTMLAnchorElement: A.AnchorElement, HTMLAreaElement: A.AreaElement, Blob: A.Blob, CDATASection: A.CharacterData, CharacterData: A.CharacterData, Comment: A.CharacterData, ProcessingInstruction: A.CharacterData, Text: A.CharacterData, CSSPerspective: A.CssPerspective, CSSCharsetRule: A.CssRule, CSSConditionRule: A.CssRule, CSSFontFaceRule: A.CssRule, CSSGroupingRule: A.CssRule, CSSImportRule: A.CssRule, CSSKeyframeRule: A.CssRule, MozCSSKeyframeRule: A.CssRule, WebKitCSSKeyframeRule: A.CssRule, CSSKeyframesRule: A.CssRule, MozCSSKeyframesRule: A.CssRule, WebKitCSSKeyframesRule: A.CssRule, CSSMediaRule: A.CssRule, CSSNamespaceRule: A.CssRule, CSSPageRule: A.CssRule, CSSRule: A.CssRule, CSSStyleRule: A.CssRule, CSSSupportsRule: A.CssRule, CSSViewportRule: A.CssRule, CSSStyleDeclaration: A.CssStyleDeclaration, MSStyleCSSProperties: A.CssStyleDeclaration, CSS2Properties: A.CssStyleDeclaration, CSSImageValue: A.CssStyleValue, CSSKeywordValue: A.CssStyleValue, CSSNumericValue: A.CssStyleValue, CSSPositionValue: A.CssStyleValue, CSSResourceValue: A.CssStyleValue, CSSUnitValue: A.CssStyleValue, CSSURLImageValue: A.CssStyleValue, CSSStyleValue: A.CssStyleValue, CSSMatrixComponent: A.CssTransformComponent, CSSRotation: A.CssTransformComponent, CSSScale: A.CssTransformComponent, CSSSkew: A.CssTransformComponent, CSSTranslation: A.CssTransformComponent, CSSTransformComponent: A.CssTransformComponent, CSSTransformValue: A.CssTransformValue, CSSUnparsedValue: A.CssUnparsedValue, DataTransferItemList: A.DataTransferItemList, DOMException: A.DomException, ClientRectList: A.DomRectList, DOMRectList: A.DomRectList, DOMRectReadOnly: A.DomRectReadOnly, DOMStringList: A.DomStringList, DOMTokenList: A.DomTokenList, MathMLElement: A.Element, SVGAElement: A.Element, SVGAnimateElement: A.Element, SVGAnimateMotionElement: A.Element, SVGAnimateTransformElement: A.Element, SVGAnimationElement: A.Element, SVGCircleElement: A.Element, SVGClipPathElement: A.Element, SVGDefsElement: A.Element, SVGDescElement: A.Element, SVGDiscardElement: A.Element, SVGEllipseElement: A.Element, SVGFEBlendElement: A.Element, SVGFEColorMatrixElement: A.Element, SVGFEComponentTransferElement: A.Element, SVGFECompositeElement: A.Element, SVGFEConvolveMatrixElement: A.Element, SVGFEDiffuseLightingElement: A.Element, SVGFEDisplacementMapElement: A.Element, SVGFEDistantLightElement: A.Element, SVGFEFloodElement: A.Element, SVGFEFuncAElement: A.Element, SVGFEFuncBElement: A.Element, SVGFEFuncGElement: A.Element, SVGFEFuncRElement: A.Element, SVGFEGaussianBlurElement: A.Element, SVGFEImageElement: A.Element, SVGFEMergeElement: A.Element, SVGFEMergeNodeElement: A.Element, SVGFEMorphologyElement: A.Element, SVGFEOffsetElement: A.Element, SVGFEPointLightElement: A.Element, SVGFESpecularLightingElement: A.Element, SVGFESpotLightElement: A.Element, SVGFETileElement: A.Element, SVGFETurbulenceElement: A.Element, SVGFilterElement: A.Element, SVGForeignObjectElement: A.Element, SVGGElement: A.Element, SVGGeometryElement: A.Element, SVGGraphicsElement: A.Element, SVGImageElement: A.Element, SVGLineElement: A.Element, SVGLinearGradientElement: A.Element, SVGMarkerElement: A.Element, SVGMaskElement: A.Element, SVGMetadataElement: A.Element, SVGPathElement: A.Element, SVGPatternElement: A.Element, SVGPolygonElement: A.Element, SVGPolylineElement: A.Element, SVGRadialGradientElement: A.Element, SVGRectElement: A.Element, SVGScriptElement: A.Element, SVGSetElement: A.Element, SVGStopElement: A.Element, SVGStyleElement: A.Element, SVGElement: A.Element, SVGSVGElement: A.Element, SVGSwitchElement: A.Element, SVGSymbolElement: A.Element, SVGTSpanElement: A.Element, SVGTextContentElement: A.Element, SVGTextElement: A.Element, SVGTextPathElement: A.Element, SVGTextPositioningElement: A.Element, SVGTitleElement: A.Element, SVGUseElement: A.Element, SVGViewElement: A.Element, SVGGradientElement: A.Element, SVGComponentTransferFunctionElement: A.Element, SVGFEDropShadowElement: A.Element, SVGMPathElement: A.Element, Element: A.Element, AbsoluteOrientationSensor: A.EventTarget, Accelerometer: A.EventTarget, AccessibleNode: A.EventTarget, AmbientLightSensor: A.EventTarget, Animation: A.EventTarget, ApplicationCache: A.EventTarget, DOMApplicationCache: A.EventTarget, OfflineResourceList: A.EventTarget, BackgroundFetchRegistration: A.EventTarget, BatteryManager: A.EventTarget, BroadcastChannel: A.EventTarget, CanvasCaptureMediaStreamTrack: A.EventTarget, DedicatedWorkerGlobalScope: A.EventTarget, EventSource: A.EventTarget, FileReader: A.EventTarget, FontFaceSet: A.EventTarget, Gyroscope: A.EventTarget, XMLHttpRequest: A.EventTarget, XMLHttpRequestEventTarget: A.EventTarget, XMLHttpRequestUpload: A.EventTarget, LinearAccelerationSensor: A.EventTarget, Magnetometer: A.EventTarget, MediaDevices: A.EventTarget, MediaKeySession: A.EventTarget, MediaQueryList: A.EventTarget, MediaRecorder: A.EventTarget, MediaSource: A.EventTarget, MediaStream: A.EventTarget, MediaStreamTrack: A.EventTarget, MessagePort: A.EventTarget, MIDIAccess: A.EventTarget, MIDIInput: A.EventTarget, MIDIOutput: A.EventTarget, MIDIPort: A.EventTarget, NetworkInformation: A.EventTarget, Notification: A.EventTarget, OffscreenCanvas: A.EventTarget, OrientationSensor: A.EventTarget, PaymentRequest: A.EventTarget, Performance: A.EventTarget, PermissionStatus: A.EventTarget, PresentationAvailability: A.EventTarget, PresentationConnection: A.EventTarget, PresentationConnectionList: A.EventTarget, PresentationRequest: A.EventTarget, RelativeOrientationSensor: A.EventTarget, RemotePlayback: A.EventTarget, RTCDataChannel: A.EventTarget, DataChannel: A.EventTarget, RTCDTMFSender: A.EventTarget, RTCPeerConnection: A.EventTarget, webkitRTCPeerConnection: A.EventTarget, mozRTCPeerConnection: A.EventTarget, ScreenOrientation: A.EventTarget, Sensor: A.EventTarget, ServiceWorker: A.EventTarget, ServiceWorkerContainer: A.EventTarget, ServiceWorkerGlobalScope: A.EventTarget, ServiceWorkerRegistration: A.EventTarget, SharedWorker: A.EventTarget, SharedWorkerGlobalScope: A.EventTarget, SpeechRecognition: A.EventTarget, webkitSpeechRecognition: A.EventTarget, SpeechSynthesis: A.EventTarget, SpeechSynthesisUtterance: A.EventTarget, VR: A.EventTarget, VRDevice: A.EventTarget, VRDisplay: A.EventTarget, VRSession: A.EventTarget, VisualViewport: A.EventTarget, WebSocket: A.EventTarget, Window: A.EventTarget, DOMWindow: A.EventTarget, Worker: A.EventTarget, WorkerGlobalScope: A.EventTarget, WorkerPerformance: A.EventTarget, BluetoothDevice: A.EventTarget, BluetoothRemoteGATTCharacteristic: A.EventTarget, Clipboard: A.EventTarget, MojoInterfaceInterceptor: A.EventTarget, USB: A.EventTarget, IDBDatabase: A.EventTarget, IDBOpenDBRequest: A.EventTarget, IDBVersionChangeRequest: A.EventTarget, IDBRequest: A.EventTarget, IDBTransaction: A.EventTarget, AnalyserNode: A.EventTarget, RealtimeAnalyserNode: A.EventTarget, AudioBufferSourceNode: A.EventTarget, AudioDestinationNode: A.EventTarget, AudioNode: A.EventTarget, AudioScheduledSourceNode: A.EventTarget, AudioWorkletNode: A.EventTarget, BiquadFilterNode: A.EventTarget, ChannelMergerNode: A.EventTarget, AudioChannelMerger: A.EventTarget, ChannelSplitterNode: A.EventTarget, AudioChannelSplitter: A.EventTarget, ConstantSourceNode: A.EventTarget, ConvolverNode: A.EventTarget, DelayNode: A.EventTarget, DynamicsCompressorNode: A.EventTarget, GainNode: A.EventTarget, AudioGainNode: A.EventTarget, IIRFilterNode: A.EventTarget, MediaElementAudioSourceNode: A.EventTarget, MediaStreamAudioDestinationNode: A.EventTarget, MediaStreamAudioSourceNode: A.EventTarget, OscillatorNode: A.EventTarget, Oscillator: A.EventTarget, PannerNode: A.EventTarget, AudioPannerNode: A.EventTarget, webkitAudioPannerNode: A.EventTarget, ScriptProcessorNode: A.EventTarget, JavaScriptAudioNode: A.EventTarget, StereoPannerNode: A.EventTarget, WaveShaperNode: A.EventTarget, EventTarget: A.EventTarget, File: A.File, FileList: A.FileList, FileWriter: A.FileWriter, HTMLFormElement: A.FormElement, Gamepad: A.Gamepad, History: A.History, HTMLCollection: A.HtmlCollection, HTMLFormControlsCollection: A.HtmlCollection, HTMLOptionsCollection: A.HtmlCollection, Location: A.Location, MediaList: A.MediaList, MIDIInputMap: A.MidiInputMap, MIDIOutputMap: A.MidiOutputMap, MimeType: A.MimeType, MimeTypeArray: A.MimeTypeArray, Document: A.Node, DocumentFragment: A.Node, HTMLDocument: A.Node, ShadowRoot: A.Node, XMLDocument: A.Node, Attr: A.Node, DocumentType: A.Node, Node: A.Node, NodeList: A.NodeList, RadioNodeList: A.NodeList, Plugin: A.Plugin, PluginArray: A.PluginArray, RTCStatsReport: A.RtcStatsReport, HTMLSelectElement: A.SelectElement, SourceBuffer: A.SourceBuffer, SourceBufferList: A.SourceBufferList, SpeechGrammar: A.SpeechGrammar, SpeechGrammarList: A.SpeechGrammarList, SpeechRecognitionResult: A.SpeechRecognitionResult, Storage: A.Storage, CSSStyleSheet: A.StyleSheet, StyleSheet: A.StyleSheet, TextTrack: A.TextTrack, TextTrackCue: A.TextTrackCue, VTTCue: A.TextTrackCue, TextTrackCueList: A.TextTrackCueList, TextTrackList: A.TextTrackList, TimeRanges: A.TimeRanges, Touch: A.Touch, TouchList: A.TouchList, TrackDefaultList: A.TrackDefaultList, URL: A.Url, VideoTrackList: A.VideoTrackList, CSSRuleList: A._CssRuleList, ClientRect: A._DomRect, DOMRect: A._DomRect, GamepadList: A._GamepadList, NamedNodeMap: A._NamedNodeMap, MozNamedAttrMap: A._NamedNodeMap, SpeechRecognitionResultList: A._SpeechRecognitionResultList, StyleSheetList: A._StyleSheetList, SVGLength: A.Length, SVGLengthList: A.LengthList, SVGNumber: A.Number, SVGNumberList: A.NumberList, SVGPointList: A.PointList, SVGStringList: A.StringList, SVGTransform: A.Transform, SVGTransformList: A.TransformList, AudioBuffer: A.AudioBuffer, AudioParamMap: A.AudioParamMap, AudioTrackList: A.AudioTrackList, AudioContext: A.BaseAudioContext, webkitAudioContext: A.BaseAudioContext, BaseAudioContext: A.BaseAudioContext, OfflineAudioContext: A.OfflineAudioContext});
+ hunkHelpers.setOrUpdateLeafTags({WebGL: true, AbortPaymentEvent: true, AnimationEffectReadOnly: true, AnimationEffectTiming: true, AnimationEffectTimingReadOnly: true, AnimationEvent: true, AnimationPlaybackEvent: true, AnimationTimeline: true, AnimationWorkletGlobalScope: true, ApplicationCacheErrorEvent: true, AuthenticatorAssertionResponse: true, AuthenticatorAttestationResponse: true, AuthenticatorResponse: true, BackgroundFetchClickEvent: true, BackgroundFetchEvent: true, BackgroundFetchFailEvent: true, BackgroundFetchFetch: true, BackgroundFetchManager: true, BackgroundFetchSettledFetch: true, BackgroundFetchedEvent: true, BarProp: true, BarcodeDetector: true, BeforeInstallPromptEvent: true, BeforeUnloadEvent: true, BlobEvent: true, BluetoothRemoteGATTDescriptor: true, Body: true, BudgetState: true, CacheStorage: true, CanMakePaymentEvent: true, CanvasGradient: true, CanvasPattern: true, CanvasRenderingContext2D: true, Client: true, Clients: true, ClipboardEvent: true, CloseEvent: true, CompositionEvent: true, CookieStore: true, Coordinates: true, Credential: true, CredentialUserData: true, CredentialsContainer: true, Crypto: true, CryptoKey: true, CSS: true, CSSVariableReferenceValue: true, CustomElementRegistry: true, CustomEvent: true, DataTransfer: true, DataTransferItem: true, DeprecatedStorageInfo: true, DeprecatedStorageQuota: true, DeprecationReport: true, DetectedBarcode: true, DetectedFace: true, DetectedText: true, DeviceAcceleration: true, DeviceMotionEvent: true, DeviceOrientationEvent: true, DeviceRotationRate: true, DirectoryEntry: true, webkitFileSystemDirectoryEntry: true, FileSystemDirectoryEntry: true, DirectoryReader: true, WebKitDirectoryReader: true, webkitFileSystemDirectoryReader: true, FileSystemDirectoryReader: true, DocumentOrShadowRoot: true, DocumentTimeline: true, DOMError: true, DOMImplementation: true, Iterator: true, DOMMatrix: true, DOMMatrixReadOnly: true, DOMParser: true, DOMPoint: true, DOMPointReadOnly: true, DOMQuad: true, DOMStringMap: true, Entry: true, webkitFileSystemEntry: true, FileSystemEntry: true, ErrorEvent: true, Event: true, InputEvent: true, SubmitEvent: true, ExtendableEvent: true, ExtendableMessageEvent: true, External: true, FaceDetector: true, FederatedCredential: true, FetchEvent: true, FileEntry: true, webkitFileSystemFileEntry: true, FileSystemFileEntry: true, DOMFileSystem: true, WebKitFileSystem: true, webkitFileSystem: true, FileSystem: true, FocusEvent: true, FontFace: true, FontFaceSetLoadEvent: true, FontFaceSource: true, ForeignFetchEvent: true, FormData: true, GamepadButton: true, GamepadEvent: true, GamepadPose: true, Geolocation: true, Position: true, GeolocationPosition: true, HashChangeEvent: true, Headers: true, HTMLHyperlinkElementUtils: true, IdleDeadline: true, ImageBitmap: true, ImageBitmapRenderingContext: true, ImageCapture: true, ImageData: true, InputDeviceCapabilities: true, InstallEvent: true, IntersectionObserver: true, IntersectionObserverEntry: true, InterventionReport: true, KeyboardEvent: true, KeyframeEffect: true, KeyframeEffectReadOnly: true, MediaCapabilities: true, MediaCapabilitiesInfo: true, MediaDeviceInfo: true, MediaEncryptedEvent: true, MediaError: true, MediaKeyMessageEvent: true, MediaKeyStatusMap: true, MediaKeySystemAccess: true, MediaKeys: true, MediaKeysPolicy: true, MediaMetadata: true, MediaQueryListEvent: true, MediaSession: true, MediaSettingsRange: true, MediaStreamEvent: true, MediaStreamTrackEvent: true, MemoryInfo: true, MessageChannel: true, MessageEvent: true, Metadata: true, MIDIConnectionEvent: true, MIDIMessageEvent: true, MouseEvent: true, DragEvent: true, MutationEvent: true, MutationObserver: true, WebKitMutationObserver: true, MutationRecord: true, NavigationPreloadManager: true, Navigator: true, NavigatorAutomationInformation: true, NavigatorConcurrentHardware: true, NavigatorCookies: true, NavigatorUserMediaError: true, NodeFilter: true, NodeIterator: true, NonDocumentTypeChildNode: true, NonElementParentNode: true, NoncedElement: true, NotificationEvent: true, OffscreenCanvasRenderingContext2D: true, OverconstrainedError: true, PageTransitionEvent: true, PaintRenderingContext2D: true, PaintSize: true, PaintWorkletGlobalScope: true, PasswordCredential: true, Path2D: true, PaymentAddress: true, PaymentInstruments: true, PaymentManager: true, PaymentRequestEvent: true, PaymentRequestUpdateEvent: true, PaymentResponse: true, PerformanceEntry: true, PerformanceLongTaskTiming: true, PerformanceMark: true, PerformanceMeasure: true, PerformanceNavigation: true, PerformanceNavigationTiming: true, PerformanceObserver: true, PerformanceObserverEntryList: true, PerformancePaintTiming: true, PerformanceResourceTiming: true, PerformanceServerTiming: true, PerformanceTiming: true, Permissions: true, PhotoCapabilities: true, PointerEvent: true, PopStateEvent: true, PositionError: true, GeolocationPositionError: true, Presentation: true, PresentationConnectionAvailableEvent: true, PresentationConnectionCloseEvent: true, PresentationReceiver: true, ProgressEvent: true, PromiseRejectionEvent: true, PublicKeyCredential: true, PushEvent: true, PushManager: true, PushMessageData: true, PushSubscription: true, PushSubscriptionOptions: true, Range: true, RelatedApplication: true, ReportBody: true, ReportingObserver: true, ResizeObserver: true, ResizeObserverEntry: true, RTCCertificate: true, RTCDataChannelEvent: true, RTCDTMFToneChangeEvent: true, RTCIceCandidate: true, mozRTCIceCandidate: true, RTCLegacyStatsReport: true, RTCPeerConnectionIceEvent: true, RTCRtpContributingSource: true, RTCRtpReceiver: true, RTCRtpSender: true, RTCSessionDescription: true, mozRTCSessionDescription: true, RTCStatsResponse: true, RTCTrackEvent: true, Screen: true, ScrollState: true, ScrollTimeline: true, SecurityPolicyViolationEvent: true, Selection: true, SensorErrorEvent: true, SharedArrayBuffer: true, SpeechRecognitionAlternative: true, SpeechRecognitionError: true, SpeechRecognitionEvent: true, SpeechSynthesisEvent: true, SpeechSynthesisVoice: true, StaticRange: true, StorageEvent: true, StorageManager: true, StyleMedia: true, StylePropertyMap: true, StylePropertyMapReadonly: true, SyncEvent: true, SyncManager: true, TaskAttributionTiming: true, TextDetector: true, TextEvent: true, TextMetrics: true, TouchEvent: true, TrackDefault: true, TrackEvent: true, TransitionEvent: true, WebKitTransitionEvent: true, TreeWalker: true, TrustedHTML: true, TrustedScriptURL: true, TrustedURL: true, UIEvent: true, UnderlyingSourceBase: true, URLSearchParams: true, VRCoordinateSystem: true, VRDeviceEvent: true, VRDisplayCapabilities: true, VRDisplayEvent: true, VREyeParameters: true, VRFrameData: true, VRFrameOfReference: true, VRPose: true, VRSessionEvent: true, VRStageBounds: true, VRStageBoundsPoint: true, VRStageParameters: true, ValidityState: true, VideoPlaybackQuality: true, VideoTrack: true, VTTRegion: true, WheelEvent: true, WindowClient: true, WorkletAnimation: true, WorkletGlobalScope: true, XPathEvaluator: true, XPathExpression: true, XPathNSResolver: true, XPathResult: true, XMLSerializer: true, XSLTProcessor: true, Bluetooth: true, BluetoothCharacteristicProperties: true, BluetoothRemoteGATTServer: true, BluetoothRemoteGATTService: true, BluetoothUUID: true, BudgetService: true, Cache: true, DOMFileSystemSync: true, DirectoryEntrySync: true, DirectoryReaderSync: true, EntrySync: true, FileEntrySync: true, FileReaderSync: true, FileWriterSync: true, HTMLAllCollection: true, Mojo: true, MojoHandle: true, MojoInterfaceRequestEvent: true, MojoWatcher: true, NFC: true, PagePopupController: true, Report: true, Request: true, ResourceProgressEvent: true, Response: true, SubtleCrypto: true, USBAlternateInterface: true, USBConfiguration: true, USBConnectionEvent: true, USBDevice: true, USBEndpoint: true, USBInTransferResult: true, USBInterface: true, USBIsochronousInTransferPacket: true, USBIsochronousInTransferResult: true, USBIsochronousOutTransferPacket: true, USBIsochronousOutTransferResult: true, USBOutTransferResult: true, WorkerLocation: true, WorkerNavigator: true, Worklet: true, IDBCursor: true, IDBCursorWithValue: true, IDBFactory: true, IDBIndex: true, IDBKeyRange: true, IDBObjectStore: true, IDBObservation: true, IDBObserver: true, IDBObserverChanges: true, IDBVersionChangeEvent: true, SVGAngle: true, SVGAnimatedAngle: true, SVGAnimatedBoolean: true, SVGAnimatedEnumeration: true, SVGAnimatedInteger: true, SVGAnimatedLength: true, SVGAnimatedLengthList: true, SVGAnimatedNumber: true, SVGAnimatedNumberList: true, SVGAnimatedPreserveAspectRatio: true, SVGAnimatedRect: true, SVGAnimatedString: true, SVGAnimatedTransformList: true, SVGMatrix: true, SVGPoint: true, SVGPreserveAspectRatio: true, SVGRect: true, SVGUnitTypes: true, AudioListener: true, AudioParam: true, AudioProcessingEvent: true, AudioTrack: true, AudioWorkletGlobalScope: true, AudioWorkletProcessor: true, OfflineAudioCompletionEvent: true, PeriodicWave: true, WebGLActiveInfo: true, ANGLEInstancedArrays: true, ANGLE_instanced_arrays: true, WebGLBuffer: true, WebGLCanvas: true, WebGLColorBufferFloat: true, WebGLCompressedTextureASTC: true, WebGLCompressedTextureATC: true, WEBGL_compressed_texture_atc: true, WebGLCompressedTextureETC1: true, WEBGL_compressed_texture_etc1: true, WebGLCompressedTextureETC: true, WebGLCompressedTexturePVRTC: true, WEBGL_compressed_texture_pvrtc: true, WebGLCompressedTextureS3TC: true, WEBGL_compressed_texture_s3tc: true, WebGLCompressedTextureS3TCsRGB: true, WebGLContextEvent: true, WebGLDebugRendererInfo: true, WEBGL_debug_renderer_info: true, WebGLDebugShaders: true, WEBGL_debug_shaders: true, WebGLDepthTexture: true, WEBGL_depth_texture: true, WebGLDrawBuffers: true, WEBGL_draw_buffers: true, EXTsRGB: true, EXT_sRGB: true, EXTBlendMinMax: true, EXT_blend_minmax: true, EXTColorBufferFloat: true, EXTColorBufferHalfFloat: true, EXTDisjointTimerQuery: true, EXTDisjointTimerQueryWebGL2: true, EXTFragDepth: true, EXT_frag_depth: true, EXTShaderTextureLOD: true, EXT_shader_texture_lod: true, EXTTextureFilterAnisotropic: true, EXT_texture_filter_anisotropic: true, WebGLFramebuffer: true, WebGLGetBufferSubDataAsync: true, WebGLLoseContext: true, WebGLExtensionLoseContext: true, WEBGL_lose_context: true, OESElementIndexUint: true, OES_element_index_uint: true, OESStandardDerivatives: true, OES_standard_derivatives: true, OESTextureFloat: true, OES_texture_float: true, OESTextureFloatLinear: true, OES_texture_float_linear: true, OESTextureHalfFloat: true, OES_texture_half_float: true, OESTextureHalfFloatLinear: true, OES_texture_half_float_linear: true, OESVertexArrayObject: true, OES_vertex_array_object: true, WebGLProgram: true, WebGLQuery: true, WebGLRenderbuffer: true, WebGLRenderingContext: true, WebGL2RenderingContext: true, WebGLSampler: true, WebGLShader: true, WebGLShaderPrecisionFormat: true, WebGLSync: true, WebGLTexture: true, WebGLTimerQueryEXT: true, WebGLTransformFeedback: true, WebGLUniformLocation: true, WebGLVertexArrayObject: true, WebGLVertexArrayObjectOES: true, WebGL2RenderingContextBase: true, ArrayBuffer: true, ArrayBufferView: false, DataView: true, Float32Array: true, Float64Array: true, Int16Array: true, Int32Array: true, Int8Array: true, Uint16Array: true, Uint32Array: true, Uint8ClampedArray: true, CanvasPixelArray: true, Uint8Array: false, HTMLAudioElement: true, HTMLBRElement: true, HTMLBaseElement: true, HTMLBodyElement: true, HTMLButtonElement: true, HTMLCanvasElement: true, HTMLContentElement: true, HTMLDListElement: true, HTMLDataElement: true, HTMLDataListElement: true, HTMLDetailsElement: true, HTMLDialogElement: true, HTMLDivElement: true, HTMLEmbedElement: true, HTMLFieldSetElement: true, HTMLHRElement: true, HTMLHeadElement: true, HTMLHeadingElement: true, HTMLHtmlElement: true, HTMLIFrameElement: true, HTMLImageElement: true, HTMLInputElement: true, HTMLLIElement: true, HTMLLabelElement: true, HTMLLegendElement: true, HTMLLinkElement: true, HTMLMapElement: true, HTMLMediaElement: true, HTMLMenuElement: true, HTMLMetaElement: true, HTMLMeterElement: true, HTMLModElement: true, HTMLOListElement: true, HTMLObjectElement: true, HTMLOptGroupElement: true, HTMLOptionElement: true, HTMLOutputElement: true, HTMLParagraphElement: true, HTMLParamElement: true, HTMLPictureElement: true, HTMLPreElement: true, HTMLProgressElement: true, HTMLQuoteElement: true, HTMLScriptElement: true, HTMLShadowElement: true, HTMLSlotElement: true, HTMLSourceElement: true, HTMLSpanElement: true, HTMLStyleElement: true, HTMLTableCaptionElement: true, HTMLTableCellElement: true, HTMLTableDataCellElement: true, HTMLTableHeaderCellElement: true, HTMLTableColElement: true, HTMLTableElement: true, HTMLTableRowElement: true, HTMLTableSectionElement: true, HTMLTemplateElement: true, HTMLTextAreaElement: true, HTMLTimeElement: true, HTMLTitleElement: true, HTMLTrackElement: true, HTMLUListElement: true, HTMLUnknownElement: true, HTMLVideoElement: true, HTMLDirectoryElement: true, HTMLFontElement: true, HTMLFrameElement: true, HTMLFrameSetElement: true, HTMLMarqueeElement: true, HTMLElement: false, AccessibleNodeList: true, HTMLAnchorElement: true, HTMLAreaElement: true, Blob: false, CDATASection: true, CharacterData: true, Comment: true, ProcessingInstruction: true, Text: true, CSSPerspective: true, CSSCharsetRule: true, CSSConditionRule: true, CSSFontFaceRule: true, CSSGroupingRule: true, CSSImportRule: true, CSSKeyframeRule: true, MozCSSKeyframeRule: true, WebKitCSSKeyframeRule: true, CSSKeyframesRule: true, MozCSSKeyframesRule: true, WebKitCSSKeyframesRule: true, CSSMediaRule: true, CSSNamespaceRule: true, CSSPageRule: true, CSSRule: true, CSSStyleRule: true, CSSSupportsRule: true, CSSViewportRule: true, CSSStyleDeclaration: true, MSStyleCSSProperties: true, CSS2Properties: true, CSSImageValue: true, CSSKeywordValue: true, CSSNumericValue: true, CSSPositionValue: true, CSSResourceValue: true, CSSUnitValue: true, CSSURLImageValue: true, CSSStyleValue: false, CSSMatrixComponent: true, CSSRotation: true, CSSScale: true, CSSSkew: true, CSSTranslation: true, CSSTransformComponent: false, CSSTransformValue: true, CSSUnparsedValue: true, DataTransferItemList: true, DOMException: true, ClientRectList: true, DOMRectList: true, DOMRectReadOnly: false, DOMStringList: true, DOMTokenList: true, MathMLElement: true, SVGAElement: true, SVGAnimateElement: true, SVGAnimateMotionElement: true, SVGAnimateTransformElement: true, SVGAnimationElement: true, SVGCircleElement: true, SVGClipPathElement: true, SVGDefsElement: true, SVGDescElement: true, SVGDiscardElement: true, SVGEllipseElement: true, SVGFEBlendElement: true, SVGFEColorMatrixElement: true, SVGFEComponentTransferElement: true, SVGFECompositeElement: true, SVGFEConvolveMatrixElement: true, SVGFEDiffuseLightingElement: true, SVGFEDisplacementMapElement: true, SVGFEDistantLightElement: true, SVGFEFloodElement: true, SVGFEFuncAElement: true, SVGFEFuncBElement: true, SVGFEFuncGElement: true, SVGFEFuncRElement: true, SVGFEGaussianBlurElement: true, SVGFEImageElement: true, SVGFEMergeElement: true, SVGFEMergeNodeElement: true, SVGFEMorphologyElement: true, SVGFEOffsetElement: true, SVGFEPointLightElement: true, SVGFESpecularLightingElement: true, SVGFESpotLightElement: true, SVGFETileElement: true, SVGFETurbulenceElement: true, SVGFilterElement: true, SVGForeignObjectElement: true, SVGGElement: true, SVGGeometryElement: true, SVGGraphicsElement: true, SVGImageElement: true, SVGLineElement: true, SVGLinearGradientElement: true, SVGMarkerElement: true, SVGMaskElement: true, SVGMetadataElement: true, SVGPathElement: true, SVGPatternElement: true, SVGPolygonElement: true, SVGPolylineElement: true, SVGRadialGradientElement: true, SVGRectElement: true, SVGScriptElement: true, SVGSetElement: true, SVGStopElement: true, SVGStyleElement: true, SVGElement: true, SVGSVGElement: true, SVGSwitchElement: true, SVGSymbolElement: true, SVGTSpanElement: true, SVGTextContentElement: true, SVGTextElement: true, SVGTextPathElement: true, SVGTextPositioningElement: true, SVGTitleElement: true, SVGUseElement: true, SVGViewElement: true, SVGGradientElement: true, SVGComponentTransferFunctionElement: true, SVGFEDropShadowElement: true, SVGMPathElement: true, Element: false, AbsoluteOrientationSensor: true, Accelerometer: true, AccessibleNode: true, AmbientLightSensor: true, Animation: true, ApplicationCache: true, DOMApplicationCache: true, OfflineResourceList: true, BackgroundFetchRegistration: true, BatteryManager: true, BroadcastChannel: true, CanvasCaptureMediaStreamTrack: true, DedicatedWorkerGlobalScope: true, EventSource: true, FileReader: true, FontFaceSet: true, Gyroscope: true, XMLHttpRequest: true, XMLHttpRequestEventTarget: true, XMLHttpRequestUpload: true, LinearAccelerationSensor: true, Magnetometer: true, MediaDevices: true, MediaKeySession: true, MediaQueryList: true, MediaRecorder: true, MediaSource: true, MediaStream: true, MediaStreamTrack: true, MessagePort: true, MIDIAccess: true, MIDIInput: true, MIDIOutput: true, MIDIPort: true, NetworkInformation: true, Notification: true, OffscreenCanvas: true, OrientationSensor: true, PaymentRequest: true, Performance: true, PermissionStatus: true, PresentationAvailability: true, PresentationConnection: true, PresentationConnectionList: true, PresentationRequest: true, RelativeOrientationSensor: true, RemotePlayback: true, RTCDataChannel: true, DataChannel: true, RTCDTMFSender: true, RTCPeerConnection: true, webkitRTCPeerConnection: true, mozRTCPeerConnection: true, ScreenOrientation: true, Sensor: true, ServiceWorker: true, ServiceWorkerContainer: true, ServiceWorkerGlobalScope: true, ServiceWorkerRegistration: true, SharedWorker: true, SharedWorkerGlobalScope: true, SpeechRecognition: true, webkitSpeechRecognition: true, SpeechSynthesis: true, SpeechSynthesisUtterance: true, VR: true, VRDevice: true, VRDisplay: true, VRSession: true, VisualViewport: true, WebSocket: true, Window: true, DOMWindow: true, Worker: true, WorkerGlobalScope: true, WorkerPerformance: true, BluetoothDevice: true, BluetoothRemoteGATTCharacteristic: true, Clipboard: true, MojoInterfaceInterceptor: true, USB: true, IDBDatabase: true, IDBOpenDBRequest: true, IDBVersionChangeRequest: true, IDBRequest: true, IDBTransaction: true, AnalyserNode: true, RealtimeAnalyserNode: true, AudioBufferSourceNode: true, AudioDestinationNode: true, AudioNode: true, AudioScheduledSourceNode: true, AudioWorkletNode: true, BiquadFilterNode: true, ChannelMergerNode: true, AudioChannelMerger: true, ChannelSplitterNode: true, AudioChannelSplitter: true, ConstantSourceNode: true, ConvolverNode: true, DelayNode: true, DynamicsCompressorNode: true, GainNode: true, AudioGainNode: true, IIRFilterNode: true, MediaElementAudioSourceNode: true, MediaStreamAudioDestinationNode: true, MediaStreamAudioSourceNode: true, OscillatorNode: true, Oscillator: true, PannerNode: true, AudioPannerNode: true, webkitAudioPannerNode: true, ScriptProcessorNode: true, JavaScriptAudioNode: true, StereoPannerNode: true, WaveShaperNode: true, EventTarget: false, File: true, FileList: true, FileWriter: true, HTMLFormElement: true, Gamepad: true, History: true, HTMLCollection: true, HTMLFormControlsCollection: true, HTMLOptionsCollection: true, Location: true, MediaList: true, MIDIInputMap: true, MIDIOutputMap: true, MimeType: true, MimeTypeArray: true, Document: true, DocumentFragment: true, HTMLDocument: true, ShadowRoot: true, XMLDocument: true, Attr: true, DocumentType: true, Node: false, NodeList: true, RadioNodeList: true, Plugin: true, PluginArray: true, RTCStatsReport: true, HTMLSelectElement: true, SourceBuffer: true, SourceBufferList: true, SpeechGrammar: true, SpeechGrammarList: true, SpeechRecognitionResult: true, Storage: true, CSSStyleSheet: true, StyleSheet: true, TextTrack: true, TextTrackCue: true, VTTCue: true, TextTrackCueList: true, TextTrackList: true, TimeRanges: true, Touch: true, TouchList: true, TrackDefaultList: true, URL: true, VideoTrackList: true, CSSRuleList: true, ClientRect: true, DOMRect: true, GamepadList: true, NamedNodeMap: true, MozNamedAttrMap: true, SpeechRecognitionResultList: true, StyleSheetList: true, SVGLength: true, SVGLengthList: true, SVGNumber: true, SVGNumberList: true, SVGPointList: true, SVGStringList: true, SVGTransform: true, SVGTransformList: true, AudioBuffer: true, AudioParamMap: true, AudioTrackList: true, AudioContext: true, webkitAudioContext: true, BaseAudioContext: false, OfflineAudioContext: true});
+ A.NativeTypedArray.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfDouble_NativeTypedArray_ListMixin_FixedLengthListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A.NativeTypedArrayOfDouble.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A._NativeTypedArrayOfInt_NativeTypedArray_ListMixin_FixedLengthListMixin.$nativeSuperclassTag = "ArrayBufferView";
+ A.NativeTypedArrayOfInt.$nativeSuperclassTag = "ArrayBufferView";
+ A._SourceBufferList_EventTarget_ListMixin.$nativeSuperclassTag = "EventTarget";
+ A._SourceBufferList_EventTarget_ListMixin_ImmutableListMixin.$nativeSuperclassTag = "EventTarget";
+ A._TextTrackList_EventTarget_ListMixin.$nativeSuperclassTag = "EventTarget";
+ A._TextTrackList_EventTarget_ListMixin_ImmutableListMixin.$nativeSuperclassTag = "EventTarget";
+ })();
+ Function.prototype.call$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$2 = function(a, b) {
+ return this(a, b);
+ };
+ Function.prototype.call$0 = function() {
+ return this();
+ };
+ Function.prototype.call$1$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$3$3 = function(a, b, c) {
+ return this(a, b, c);
+ };
+ Function.prototype.call$5 = function(a, b, c, d, e) {
+ return this(a, b, c, d, e);
+ };
+ Function.prototype.call$3 = function(a, b, c) {
+ return this(a, b, c);
+ };
+ Function.prototype.call$3$6 = function(a, b, c, d, e, f) {
+ return this(a, b, c, d, e, f);
+ };
+ Function.prototype.call$1$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$2$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$2$5 = function(a, b, c, d, e) {
+ return this(a, b, c, d, e);
+ };
+ Function.prototype.call$2$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$2$2 = function(a, b) {
+ return this(a, b);
+ };
+ Function.prototype.call$3$1 = function(a) {
+ return this(a);
+ };
+ Function.prototype.call$3$4 = function(a, b, c, d) {
+ return this(a, b, c, d);
+ };
+ Function.prototype.call$1$2 = function(a, b) {
+ return this(a, b);
+ };
+ Function.prototype.call$1$0 = function() {
+ return this();
+ };
+ Function.prototype.call$2$3 = function(a, b, c) {
+ return this(a, b, c);
+ };
+ convertAllToFastObject(holders);
+ convertToFastObject($);
+ (function(callback) {
+ if (typeof document === "undefined") {
+ callback(null);
+ return;
+ }
+ if (typeof document.currentScript != "undefined") {
+ callback(document.currentScript);
+ return;
+ }
+ var scripts = document.scripts;
+ function onLoad(event) {
+ for (var i = 0; i < scripts.length; ++i)
+ scripts[i].removeEventListener("load", onLoad, false);
+ callback(event.target);
+ }
+ for (var i = 0; i < scripts.length; ++i)
+ scripts[i].addEventListener("load", onLoad, false);
+ })(function(currentScript) {
+ init.currentScript = currentScript;
+ var callMain = A.main;
+ if (typeof dartMainRunner === "function")
+ dartMainRunner(callMain, []);
+ else
+ callMain([]);
+ });
+})();
+
+//# sourceMappingURL=host.dart.js.map
diff --git a/pkgs/test/lib/src/runner/browser/static/index.html b/pkgs/test/lib/src/runner/browser/static/index.html
new file mode 100644
index 0000000..69617fa
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/static/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>test Browser Host</title>
+ <link rel="stylesheet" type="text/css" href="host.css" />
+</head>
+<body>
+ <svg id="dart" version="1.1" x="0px" y="0px" width="400px" height="400px" viewBox="0 0 400 400">
+ <path id="right-flank" fill="#0083C9" d="M249.379,226.486l-6.676,15.572L166.174,166h58.82c0,0,2.807-0.409,3.645,1.966L249.379,226.486z"/>
+ <path id="right-ear" fill="#00D2B8" d="M201.84,141.906L166.174,166h58.82c0,0,2.168-0.25,2.645,0.566l-2.694-8.848l-15.024-14.68C207.555,140.329,203.578,140.744,201.84,141.906z"/>
+ <path id="left-flank" fill="#00D2B8" d="M242.616,241.856l-15.022,6.799l-60.493-21.429c-1.035-0.395-1.101-3.696-1.101-3.696v-57.932L242.616,241.856z"/>
+ <path id="left-paw" fill="#55DECA" d="M167.003,227.098l60.636,21.558l15.064-6.799L237.224,259h-43.856c0,0-14.077-13.929-18.141-17.993C171.162,236.943,169.162,233.989,167.003,227.098z"/>
+ <path id="right-paw" fill="#00A4E4" d="M227.676,166.365c0.963,1.401,1.361,2.473,1.361,2.473l20.352,57.648l-6.711,15.37L259,236.463v-44.854c0,0-13.678-13.965-17.741-17.882C237.193,169.811,231.466,166.319,227.676,166.365z"/>
+ <path id="left-ear" fill="#0083C9" d="M166.769,227.098c0,0-0.769-1.104-0.769-4.355v-57.144l-23.115,34.877c-1.626,1.774-1.567,6.538,1.595,9.755l13.636,13.892L166.769,227.098z"/>
+ </svg>
+ <div id="dark"></div>
+ <svg id="play" version="1.1" x="0px" y="0px" width="80px" height="80px" viewBox="0 0 25 25">
+ <defs><filter id="blur"><feGaussianBlur stdDeviation="0.3" id="feGaussianBlur5097" /></filter></defs>
+ <path d="M 3.777014,1.3715789 A 1.1838119,1.1838119 0 0 0 2.693923,2.5488509 V 22.444746 a 1.1838119,1.1838119 0 0 0 1.765908,1.035999 l 17.235259,-9.95972 a 1.1838119,1.1838119 0 0 0 0,-2.071998 L 4.459831,1.5128519 A 1.1838119,1.1838119 0 0 0 3.777014,1.3715789 z" style="opacity:0.5;stroke:#000000;stroke-width:1;filter:url(#blur)" />
+ <path style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.32722104" d="M 3.4770491,1.0714664 A 1.1838119,1.1838119 0 0 0 2.3939589,2.2487382 V 22.144633 a 1.1838119,1.1838119 0 0 0 1.7659079,1.035999 l 17.2352602,-9.95972 a 1.1838119,1.1838119 0 0 0 0,-2.071998 L 4.1598668,1.2127389 A 1.1838119,1.1838119 0 0 0 3.4770491,1.0714664 z" />
+ </svg>
+ <script src="host.dart.js"></script>
+</body>
+</html>
diff --git a/pkgs/test/lib/src/runner/browser/static/run_wasm_chrome.js b/pkgs/test/lib/src/runner/browser/static/run_wasm_chrome.js
new file mode 100644
index 0000000..6e3bda0
--- /dev/null
+++ b/pkgs/test/lib/src/runner/browser/static/run_wasm_chrome.js
@@ -0,0 +1,47 @@
+// Copyright (c) 2022, 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.
+
+// TODO(joshualitt): Investigate making this a module. Currently, Dart2Wasm is
+// broken in D8 with modules because of an issue with async. This may or may not
+// affect chrome.
+(async () => {
+ // Fetch and compile Wasm binary.
+ let data = document.getElementById("WasmBootstrapInfo").dataset;
+
+ // Instantiate the Dart module, importing from the global scope.
+ let dart2wasmJsRuntime = await import("./" + data.jsruntimeurl);
+
+ // Support three versions of dart2wasm:
+ //
+ // (1) Versions before 3.6.0-167.0.dev require the user to compile using the
+ // browser's `WebAssembly` API, the compiled module needs to be instantiated
+ // using the JS runtime.
+ //
+ // (2) Versions starting with 3.6.0-167.0.dev added helpers for compiling and
+ // instantiating.
+ //
+ // (3) Versions starting with 3.6.0-212.0.dev made compilation functions
+ // return a new type that comes with instantiation and invoke methods.
+
+ if (dart2wasmJsRuntime.compileStreaming !== undefined) {
+ // Version (2) or (3).
+ let compiledModule = await dart2wasmJsRuntime.compileStreaming(
+ fetch(data.wasmurl),
+ );
+ if (compiledModule.instantiate !== undefined) {
+ // Version (3).
+ let instantiatedModule = await compiledModule.instantiate();
+ instantiatedModule.invokeMain();
+ } else {
+ // Version (2).
+ let dartInstance = await dart2wasmJsRuntime.instantiate(compiledModule, {});
+ await dart2wasmJsRuntime.invoke(dartInstance);
+ }
+ } else {
+ // Version (1).
+ let modulePromise = WebAssembly.compileStreaming(fetch(data.wasmurl));
+ let dartInstance = await dart2wasmJsRuntime.instantiate(modulePromise, {});
+ await dart2wasmJsRuntime.invoke(dartInstance);
+ }
+})();
diff --git a/pkgs/test/lib/src/runner/executable_settings.dart b/pkgs/test/lib/src/runner/executable_settings.dart
new file mode 100644
index 0000000..904dc51
--- /dev/null
+++ b/pkgs/test/lib/src/runner/executable_settings.dart
@@ -0,0 +1,224 @@
+// 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.
+
+import 'dart:io';
+
+import 'package:io/io.dart';
+import 'package:path/path.dart' as p;
+import 'package:source_span/source_span.dart';
+import 'package:yaml/yaml.dart';
+
+/// User-provided settings for invoking an executable.
+class ExecutableSettings {
+ /// Additional arguments to pass to the executable.
+ final List<String> arguments;
+
+ /// The path to the executable on Linux.
+ ///
+ /// This may be an absolute path or a basename, in which case it will be
+ /// looked up on the system path. It may not be relative.
+ final String? _linuxExecutable;
+
+ /// Potential commands to execute on Mac OS to launch this executable.
+ ///
+ /// The values may be an absolute path, or a basename.
+ /// The chosen command will be the first value from this list which either is
+ /// a full path that exists, or is a basename. When a basename is included it
+ /// should always be at the end of the list because it will always terminate
+ /// the search through the list. Relative paths are not allowed.
+ final List<String>? _macOSExectuables;
+
+ /// The path to the executable on Windows.
+ ///
+ /// This may be an absolute path; a basename, in which case it will be looked
+ /// up on the system path; or a relative path, in which case it will be looked
+ /// up relative to the paths in the `LOCALAPPDATA`, `PROGRAMFILES`, and
+ /// `PROGRAMFILES(X64)` environment variables.
+ final String? _windowsExecutable;
+
+ /// The environment variable, if any, to use as an override for the default
+ /// path.
+ final String? _environmentOverride;
+
+ /// The path to the executable for the current operating system.
+ String get executable {
+ if (_environmentOverride != null) {
+ final envVariable = Platform.environment[_environmentOverride];
+ if (envVariable != null) return envVariable;
+ }
+
+ if (Platform.isMacOS) {
+ if (_macOSExectuables != null) {
+ for (final path in _macOSExectuables) {
+ if (p.basename(path) == path) return path;
+ if (p.isAbsolute(path)) {
+ if (File(path).existsSync()) return path;
+ } else {
+ throw ArgumentError(
+ 'Mac OS executable must be a basename or an absolute path.'
+ ' Got relative path: $path');
+ }
+ }
+ }
+ throw ArgumentError('Could not find a command basename or an existing '
+ 'path in $_macOSExectuables');
+ }
+ if (!Platform.isWindows) return _linuxExecutable!;
+ final windowsExecutable = _windowsExecutable!;
+ if (p.isAbsolute(windowsExecutable)) return windowsExecutable;
+ if (p.basename(windowsExecutable) == windowsExecutable) {
+ return windowsExecutable;
+ }
+
+ var prefixes = [
+ Platform.environment['LOCALAPPDATA'],
+ Platform.environment['PROGRAMFILES'],
+ Platform.environment['PROGRAMFILES(X86)']
+ ];
+
+ for (var prefix in prefixes) {
+ if (prefix == null) continue;
+
+ var path = p.join(prefix, windowsExecutable);
+ if (File(path).existsSync()) return path;
+ }
+
+ // If we can't find a path that works, return one that doesn't. This will
+ // cause an "executable not found" error to surface.
+ return p.join(
+ prefixes.firstWhere((prefix) => prefix != null, orElse: () => '.')!,
+ _windowsExecutable);
+ }
+
+ /// Whether to invoke the browser in headless mode.
+ ///
+ /// This is currently only supported by Chrome.
+ bool get headless => _headless ?? true;
+ final bool? _headless;
+
+ /// Parses settings from a user-provided YAML mapping.
+ factory ExecutableSettings.parse(YamlMap settings) {
+ List<String>? arguments;
+ var argumentsNode = settings.nodes['arguments'];
+ if (argumentsNode != null) {
+ var value = argumentsNode.value;
+ if (value is String) {
+ try {
+ arguments = shellSplit(value);
+ } on FormatException catch (error) {
+ throw SourceSpanFormatException(error.message, argumentsNode.span);
+ }
+ } else {
+ throw SourceSpanFormatException(
+ 'Must be a string.', argumentsNode.span);
+ }
+ }
+
+ String? linuxExecutable;
+ String? macOSExecutable;
+ String? windowsExecutable;
+ var executableNode = settings.nodes['executable'];
+ if (executableNode != null) {
+ var value = executableNode.value;
+ if (value is String) {
+ // Don't check this on Windows because people may want to set relative
+ // paths in their global config.
+ if (!Platform.isWindows) {
+ _assertNotRelative(executableNode as YamlScalar);
+ }
+
+ linuxExecutable = value;
+ macOSExecutable = value;
+ windowsExecutable = value;
+ } else if (executableNode is YamlMap) {
+ linuxExecutable = _getExecutable(executableNode.nodes['linux']);
+ macOSExecutable = _getExecutable(executableNode.nodes['mac_os']);
+ windowsExecutable = _getExecutable(executableNode.nodes['windows'],
+ allowRelative: true);
+ } else {
+ throw SourceSpanFormatException(
+ 'Must be a map or a string.', executableNode.span);
+ }
+ }
+
+ var headless = true;
+ var headlessNode = settings.nodes['headless'];
+ if (headlessNode != null) {
+ var value = headlessNode.value;
+ if (value is bool) {
+ headless = value;
+ } else {
+ throw SourceSpanFormatException(
+ 'Must be a boolean.', headlessNode.span);
+ }
+ }
+
+ return ExecutableSettings(
+ arguments: arguments,
+ linuxExecutable: linuxExecutable,
+ macOSExecutable: macOSExecutable,
+ windowsExecutable: windowsExecutable,
+ headless: headless);
+ }
+
+ /// Asserts that [executableNode] is a string or `null` and returns it.
+ ///
+ /// If [allowRelative] is `false` (the default), asserts that the value isn't
+ /// a relative path.
+ static String? _getExecutable(YamlNode? executableNode,
+ {bool allowRelative = false}) {
+ if (executableNode == null || executableNode.value == null) return null;
+ if (executableNode.value is! String) {
+ throw SourceSpanFormatException('Must be a string.', executableNode.span);
+ }
+ if (!allowRelative) _assertNotRelative(executableNode as YamlScalar);
+ return executableNode.value as String;
+ }
+
+ /// Throws a [SourceSpanFormatException] if [executableNode]'s value is a
+ /// relative POSIX path that's not just a plain basename.
+ ///
+ /// We loop up basenames on the PATH and we can resolve absolute paths, but we
+ /// have no way of interpreting relative paths.
+ static void _assertNotRelative(YamlScalar executableNode) {
+ var executable = executableNode.value as String;
+ if (!p.posix.isRelative(executable)) return;
+ if (p.posix.basename(executable) == executable) return;
+
+ throw SourceSpanFormatException(
+ 'Linux and Mac OS executables may not be relative paths.',
+ executableNode.span);
+ }
+
+ ExecutableSettings(
+ {Iterable<String>? arguments,
+ String? linuxExecutable,
+ String? macOSExecutable,
+ List<String>? macOSExecutables,
+ String? windowsExecutable,
+ String? environmentOverride,
+ bool? headless})
+ : arguments = arguments == null ? const [] : List.unmodifiable(arguments),
+ _linuxExecutable = linuxExecutable,
+ _macOSExectuables =
+ _normalizeMacExecutable(macOSExecutable, macOSExecutables),
+ _windowsExecutable = windowsExecutable,
+ _environmentOverride = environmentOverride,
+ _headless = headless;
+
+ /// Merges [this] with [other], with [other]'s settings taking priority.
+ ExecutableSettings merge(ExecutableSettings other) => ExecutableSettings(
+ arguments: arguments.toList()..addAll(other.arguments),
+ headless: other._headless ?? _headless,
+ linuxExecutable: other._linuxExecutable ?? _linuxExecutable,
+ macOSExecutables: other._macOSExectuables ?? _macOSExectuables,
+ windowsExecutable: other._windowsExecutable ?? _windowsExecutable);
+}
+
+List<String>? _normalizeMacExecutable(
+ String? singleArgument, List<String>? listArgument) {
+ if (listArgument != null) return listArgument;
+ if (singleArgument != null) return [singleArgument];
+ return null;
+}
diff --git a/pkgs/test/lib/src/runner/node/platform.dart b/pkgs/test/lib/src/runner/node/platform.dart
new file mode 100644
index 0000000..16cd9c6
--- /dev/null
+++ b/pkgs/test/lib/src/runner/node/platform.dart
@@ -0,0 +1,380 @@
+// 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:node_preamble/preamble.dart' as preamble;
+import 'package:package_config/package_config.dart';
+import 'package:path/path.dart' as p;
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart'
+ show Compiler, Runtime, StackTraceMapper, SuitePlatform;
+import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/dart2js_compiler_pool.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/load_exception.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/package_version.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/platform.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/customizable_platform.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/environment.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/wasm_compiler_pool.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/errors.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/package_config.dart'; // ignore: implementation_imports
+import 'package:test_core/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+import 'package:yaml/yaml.dart';
+
+import '../../util/package_map.dart';
+import '../executable_settings.dart';
+
+/// A platform that loads tests in Node.js processes.
+class NodePlatform extends PlatformPlugin
+ implements CustomizablePlatform<ExecutableSettings> {
+ /// The test runner configuration.
+ final Configuration _config;
+
+ /// The [Dart2JsCompilerPool] managing active instances of `dart2js`.
+ final _jsCompilers = Dart2JsCompilerPool(['-Dnode=true', '--server-mode']);
+ final _wasmCompilers = WasmCompilerPool(['-Dnode=true']);
+
+ /// The temporary directory in which compiled JS is emitted.
+ final _compiledDir = createTempDir();
+
+ /// Executable settings for [Runtime.nodeJS] and runtimes that extend
+ /// it.
+ final _settings = {
+ Runtime.nodeJS: ExecutableSettings(
+ linuxExecutable: 'node',
+ macOSExecutable: 'node',
+ windowsExecutable: 'node.exe')
+ };
+
+ NodePlatform() : _config = Configuration.current;
+
+ @override
+ ExecutableSettings parsePlatformSettings(YamlMap settings) =>
+ ExecutableSettings.parse(settings);
+
+ @override
+ ExecutableSettings mergePlatformSettings(
+ ExecutableSettings settings1, ExecutableSettings settings2) =>
+ settings1.merge(settings2);
+
+ @override
+ void customizePlatform(Runtime runtime, ExecutableSettings settings) {
+ var oldSettings = _settings[runtime] ?? _settings[runtime.root];
+ if (oldSettings != null) settings = oldSettings.merge(settings);
+ _settings[runtime] = settings;
+ }
+
+ @override
+ Future<RunnerSuite> load(String path, SuitePlatform platform,
+ SuiteConfiguration suiteConfig, Map<String, Object?> message) async {
+ if (platform.compiler != Compiler.dart2js &&
+ platform.compiler != Compiler.dart2wasm) {
+ throw StateError(
+ 'Unsupported compiler for the Node platform ${platform.compiler}.');
+ }
+ var (channel, stackMapper) =
+ await _loadChannel(path, platform, suiteConfig);
+ var controller = deserializeSuite(path, platform, suiteConfig,
+ const PluginEnvironment(), channel, message);
+
+ controller.channel('test.node.mapper').sink.add(stackMapper?.serialize());
+
+ return await controller.suite;
+ }
+
+ /// Loads a [StreamChannel] communicating with the test suite at [path].
+ ///
+ /// Returns that channel along with a [StackTraceMapper] representing the
+ /// source map for the compiled suite.
+ Future<(StreamChannel<Object?>, StackTraceMapper?)> _loadChannel(String path,
+ SuitePlatform platform, SuiteConfiguration suiteConfig) async {
+ final servers = await _loopback();
+
+ try {
+ var (process, stackMapper) =
+ await _spawnProcess(path, platform, suiteConfig, servers.first.port);
+
+ // Forward Node's standard IO to the print handler so it's associated with
+ // the load test.
+ //
+ // TODO(nweiz): Associate this with the current test being run, if any.
+ process.stdout.transform(lineSplitter).listen(print);
+ process.stderr.transform(lineSplitter).listen(print);
+
+ // Wait for the first connection (either over ipv4 or v6). If the proccess
+ // exits before it connects, throw instead of waiting for a connection
+ // indefinitely.
+ var socket = await Future.any([
+ StreamGroup.merge(servers).first,
+ process.exitCode.then((_) => null),
+ ]);
+
+ if (socket == null) {
+ throw LoadException(
+ path, 'Node exited before connecting to the test channel.');
+ }
+
+ var channel = StreamChannel(socket.cast<List<int>>(), socket)
+ .transform(StreamChannelTransformer.fromCodec(utf8))
+ .transform(_chunksToLines)
+ .transform(jsonDocument)
+ .transformStream(StreamTransformer.fromHandlers(handleDone: (sink) {
+ process.kill();
+ sink.close();
+ }));
+
+ return (channel, stackMapper);
+ } finally {
+ unawaited(Future.wait<void>(servers.map((s) =>
+ s.close().then<ServerSocket?>((v) => v).onError((_, __) => null))));
+ }
+ }
+
+ /// Spawns a Node.js process that loads the Dart test suite at [path].
+ ///
+ /// Returns that channel along with a [StackTraceMapper] representing the
+ /// source map for the compiled suite.
+ Future<(Process, StackTraceMapper?)> _spawnProcess(
+ String path,
+ SuitePlatform platform,
+ SuiteConfiguration suiteConfig,
+ int socketPort) async {
+ if (_config.suiteDefaults.precompiledPath != null) {
+ return _spawnPrecompiledProcess(path, platform.runtime, suiteConfig,
+ socketPort, _config.suiteDefaults.precompiledPath!);
+ } else {
+ return switch (platform.compiler) {
+ Compiler.dart2js => _spawnNormalJsProcess(
+ path, platform.runtime, suiteConfig, socketPort),
+ Compiler.dart2wasm => _spawnNormalWasmProcess(
+ path, platform.runtime, suiteConfig, socketPort),
+ _ => throw StateError('Unsupported compiler ${platform.compiler}'),
+ };
+ }
+ }
+
+ Future<String> _entrypointScriptForTest(
+ String testPath, SuiteConfiguration suiteConfig) async {
+ return '''
+ ${suiteConfig.metadata.languageVersionComment ?? await rootPackageLanguageVersionComment}
+ import "package:test/src/bootstrap/node.dart";
+
+ import "${p.toUri(p.absolute(testPath))}" as test;
+
+ void main() {
+ internalBootstrapNodeTest(() => test.main);
+ }
+ ''';
+ }
+
+ /// Compiles [testPath] with dart2js, adds the node preamble, and then spawns
+ /// a Node.js process that loads that Dart test suite.
+ Future<(Process, StackTraceMapper?)> _spawnNormalJsProcess(String testPath,
+ Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
+ var dir = Directory(_compiledDir).createTempSync('test_').path;
+ var jsPath = p.join(dir, '${p.basename(testPath)}.node_test.dart.js');
+ await _jsCompilers.compile(
+ await _entrypointScriptForTest(testPath, suiteConfig),
+ jsPath,
+ suiteConfig,
+ );
+
+ // Add the Node.js preamble to ensure that the dart2js output is
+ // compatible. Use the minified version so the source map remains valid.
+ var jsFile = File(jsPath);
+ await jsFile.writeAsString(
+ preamble.getPreamble(minified: true) + await jsFile.readAsString());
+
+ StackTraceMapper? mapper;
+ if (!suiteConfig.jsTrace) {
+ var mapPath = '$jsPath.map';
+ mapper = JSStackTraceMapper(await File(mapPath).readAsString(),
+ mapUrl: p.toUri(mapPath),
+ sdkRoot: Uri.parse('org-dartlang-sdk:///sdk'),
+ packageMap: (await currentPackageConfig).toPackageMap());
+ }
+
+ return (await _startProcess(runtime, jsPath, socketPort), mapper);
+ }
+
+ /// Compiles [testPath] with dart2wasm, adds a JS entrypoint and then spawns
+ /// a Node.js process loading the compiled test suite.
+ Future<(Process, StackTraceMapper?)> _spawnNormalWasmProcess(String testPath,
+ Runtime runtime, SuiteConfiguration suiteConfig, int socketPort) async {
+ var dir = Directory(_compiledDir).createTempSync('test_').path;
+ // dart2wasm will emit a .wasm file and a .mjs file responsible for loading
+ // that file.
+ var wasmPath = p.join(dir, '${p.basename(testPath)}.node_test.dart.wasm');
+ var loader = '${p.basename(testPath)}.node_test.dart.wasm.mjs';
+
+ // We need to create an additional entrypoint file loading the wasm module.
+ var jsPath = p.join(dir, '${p.basename(testPath)}.node_test.dart.js');
+
+ await _wasmCompilers.compile(
+ await _entrypointScriptForTest(testPath, suiteConfig),
+ wasmPath,
+ suiteConfig,
+ );
+
+ await File(jsPath).writeAsString('''
+const { createReadStream } = require('fs');
+const { once } = require('events');
+const { PassThrough } = require('stream');
+
+const main = async () => {
+ const { instantiate, invoke } = await import("./$loader");
+
+ const wasmContents = createReadStream("$wasmPath.wasm");
+ const stream = new PassThrough();
+ wasmContents.pipe(stream);
+
+ await once(wasmContents, 'open');
+ const response = new Response(
+ stream,
+ {
+ headers: {
+ "Content-Type": "application/wasm"
+ }
+ }
+ );
+ const instancePromise = WebAssembly.compileStreaming(response);
+ const module = await instantiate(instancePromise, {});
+ invoke(module);
+};
+
+main();
+''');
+
+ return (await _startProcess(runtime, jsPath, socketPort), null);
+ }
+
+ /// Spawns a Node.js process that loads the Dart test suite at [testPath]
+ /// under [precompiledPath].
+ Future<(Process, StackTraceMapper?)> _spawnPrecompiledProcess(
+ String testPath,
+ Runtime runtime,
+ SuiteConfiguration suiteConfig,
+ int socketPort,
+ String precompiledPath) async {
+ StackTraceMapper? mapper;
+ var jsPath = p.join(precompiledPath, '$testPath.node_test.dart.js');
+ if (!suiteConfig.jsTrace) {
+ var mapPath = '$jsPath.map';
+ mapper = JSStackTraceMapper(await File(mapPath).readAsString(),
+ mapUrl: p.toUri(mapPath),
+ sdkRoot: Uri.parse('org-dartlang-sdk:///sdk'),
+ packageMap: (await findPackageConfig(Directory(precompiledPath)))!
+ .toPackageMap());
+ }
+
+ return (await _startProcess(runtime, jsPath, socketPort), mapper);
+ }
+
+ /// Starts the Node.js process for [runtime] with [jsPath].
+ Future<Process> _startProcess(
+ Runtime runtime, String jsPath, int socketPort) async {
+ var settings = _settings[runtime]!;
+
+ var nodeModules = p.absolute('node_modules');
+ var nodePath = Platform.environment['NODE_PATH'];
+ nodePath = nodePath == null ? nodeModules : '$nodePath:$nodeModules';
+
+ try {
+ return await Process.start(
+ settings.executable,
+ settings.arguments.toList()
+ ..add(jsPath)
+ ..add(socketPort.toString()),
+ environment: {'NODE_PATH': nodePath});
+ } catch (error, stackTrace) {
+ await Future<Never>.error(
+ ApplicationException(
+ 'Failed to run ${runtime.name}: ${getErrorMessage(error)}'),
+ stackTrace);
+ }
+ }
+
+ @override
+ Future<void> close() => _closeMemo.runOnce(() async {
+ await _jsCompilers.close();
+ await _wasmCompilers.close();
+ await Directory(_compiledDir).deleteWithRetry();
+ });
+ final _closeMemo = AsyncMemoizer<void>();
+}
+
+Future<List<ServerSocket>> _loopback({int remainingRetries = 5}) async {
+ if (!await _supportsIPv4) {
+ return [await ServerSocket.bind(InternetAddress.loopbackIPv6, 0)];
+ }
+
+ var v4Server = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+ if (!await _supportsIPv6) return [v4Server];
+
+ try {
+ // Reuse the IPv4 server's port so that if [port] is 0, both servers use
+ // the same ephemeral port.
+ var v6Server =
+ await ServerSocket.bind(InternetAddress.loopbackIPv6, v4Server.port);
+ return [v4Server, v6Server];
+ } on SocketException catch (error) {
+ if (error.osError?.errorCode != _addressInUseErrno) rethrow;
+ if (remainingRetries == 0) rethrow;
+
+ // A port being available on IPv4 doesn't necessarily mean that the same
+ // port is available on IPv6. If it's not (which is rare in practice),
+ // we try again until we find one that's available on both.
+ unawaited(v4Server.close());
+ return await _loopback(remainingRetries: remainingRetries - 1);
+ }
+}
+
+/// Whether this computer supports binding to IPv6 addresses.
+final Future<bool> _supportsIPv6 = () async {
+ try {
+ var socket = await ServerSocket.bind(InternetAddress.loopbackIPv6, 0);
+ unawaited(socket.close());
+ return true;
+ } on SocketException catch (_) {
+ return false;
+ }
+}();
+
+/// Whether this computer supports binding to IPv4 addresses.
+final Future<bool> _supportsIPv4 = () async {
+ try {
+ var socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+ unawaited(socket.close());
+ return true;
+ } on SocketException catch (_) {
+ return false;
+ }
+}();
+
+/// The error code for an error caused by a port already being in use.
+final int _addressInUseErrno = () {
+ if (Platform.isWindows) return 10048;
+ if (Platform.isMacOS) return 48;
+ assert(Platform.isLinux);
+ return 98;
+}();
+
+/// A [StreamChannelTransformer] that converts a chunked string channel to a
+/// line-by-line channel.
+///
+/// Note that this is only safe for channels whose messages are guaranteed not
+/// to contain newlines.
+final _chunksToLines = StreamChannelTransformer<String, String>(
+ const LineSplitter(),
+ StreamSinkTransformer.fromHandlers(
+ handleData: (data, sink) => sink.add('$data\n')));
diff --git a/pkgs/test/lib/src/runner/node/socket_channel.dart b/pkgs/test/lib/src/runner/node/socket_channel.dart
new file mode 100644
index 0000000..95e81de
--- /dev/null
+++ b/pkgs/test/lib/src/runner/node/socket_channel.dart
@@ -0,0 +1,43 @@
+// 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.
+import 'dart:async';
+import 'dart:convert';
+import 'dart:js_interop';
+
+import 'package:stream_channel/stream_channel.dart';
+
+@JS('process.argv')
+external JSArray<JSString> get _args;
+
+extension type _Net._(JSObject _) {
+ external _Socket connect(int port);
+}
+
+extension type _Socket._(JSObject _) {
+ external void setEncoding(JSString encoding);
+ external void on(JSString event, JSFunction callback);
+ external void write(JSString data);
+}
+
+/// Returns a [StreamChannel] of JSON-encodable objects that communicates over a
+/// socket whose port is given by `process.argv[2]`.
+Future<StreamChannel<Object?>> socketChannel() async {
+ final net = (await importModule('node:net'.toJS).toDart) as _Net;
+
+ var socket = net.connect(int.parse(_args.toDart[2].toDart));
+ socket.setEncoding('utf8'.toJS);
+
+ var socketSink = StreamController<Object?>(sync: true)
+ ..stream.listen((event) => socket.write('${jsonEncode(event)}\n'.toJS));
+
+ var socketStream = StreamController<String>(sync: true);
+ socket.on(
+ 'data'.toJS,
+ ((JSString chunk) => socketStream.add(chunk.toDart)).toJS,
+ );
+
+ return StreamChannel.withCloseGuarantee(
+ socketStream.stream.transform(const LineSplitter()).map(jsonDecode),
+ socketSink);
+}
diff --git a/pkgs/test/lib/src/util/math.dart b/pkgs/test/lib/src/util/math.dart
new file mode 100644
index 0000000..d6bbbc7
--- /dev/null
+++ b/pkgs/test/lib/src/util/math.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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:math' as math;
+
+final _rand = math.Random();
+
+/// Returns a random 32 character alphanumeric string ([a-zA-Z0-9]), which is
+/// suitable as a url secret.
+String randomUrlSecret() {
+ var buffer = StringBuffer();
+ while (buffer.length < 32) {
+ buffer.write(_alphaChars[_rand.nextInt(_alphaChars.length)]);
+ }
+ return buffer.toString();
+}
+
+const _alphaChars =
+ '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
diff --git a/pkgs/test/lib/src/util/one_off_handler.dart b/pkgs/test/lib/src/util/one_off_handler.dart
new file mode 100644
index 0000000..7667df3
--- /dev/null
+++ b/pkgs/test/lib/src/util/one_off_handler.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2015, 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 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+
+/// A Shelf handler that provides support for one-time handlers.
+///
+/// This is useful for handlers that only expect to be hit once before becoming
+/// invalid and don't need to have a persistent URL.
+class OneOffHandler {
+ /// A map from URL paths to handlers.
+ final _handlers = <String, shelf.Handler>{};
+
+ /// The counter of handlers that have been activated.
+ var _counter = 0;
+
+ /// The actual [shelf.Handler] that dispatches requests.
+ shelf.Handler get handler => _onRequest;
+
+ /// Creates a new one-off handler that forwards to [handler].
+ ///
+ /// Returns a string that's the URL path for hitting this handler, relative to
+ /// the URL for the one-off handler itself.
+ ///
+ /// [handler] will be unmounted as soon as it receives a request.
+ String create(shelf.Handler handler) {
+ var path = _counter.toString();
+ _handlers[path] = handler;
+ _counter++;
+ return path;
+ }
+
+ /// Dispatches [request] to the appropriate handler.
+ FutureOr<shelf.Response> _onRequest(shelf.Request request) {
+ var components = p.url.split(request.url.path);
+ if (components.isEmpty) return shelf.Response.notFound(null);
+
+ var path = components.removeAt(0);
+ var handler = _handlers.remove(path);
+ if (handler == null) return shelf.Response.notFound(null);
+ return handler(request.change(path: path));
+ }
+}
diff --git a/pkgs/test/lib/src/util/package_map.dart b/pkgs/test/lib/src/util/package_map.dart
new file mode 100644
index 0000000..6b30afa
--- /dev/null
+++ b/pkgs/test/lib/src/util/package_map.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2020, 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 'package:package_config/package_config.dart';
+
+/// Adds methods to convert to a package map on [PackageConfig].
+extension PackageMap on PackageConfig {
+ /// A package map exactly matching the current package config
+ Map<String, Uri> toPackageMap() =>
+ {for (var package in packages) package.name: package.packageUriRoot};
+
+ /// A package map with all the current packages but where the uris are all
+ /// of the form 'packages/${package.name}'.
+ Map<String, Uri> toPackagesDirPackageMap() => {
+ for (var package in packages)
+ package.name: Uri.parse('packages/${package.name}')
+ };
+}
diff --git a/pkgs/test/lib/src/util/path_handler.dart b/pkgs/test/lib/src/util/path_handler.dart
new file mode 100644
index 0000000..10a8c4e
--- /dev/null
+++ b/pkgs/test/lib/src/util/path_handler.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2015, 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 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+
+/// A handler that routes to sub-handlers based on exact path prefixes.
+class PathHandler {
+ /// A trie of path components to handlers.
+ final _paths = _Node();
+
+ /// The shelf handler.
+ shelf.Handler get handler => _onRequest;
+
+ /// Returns middleware that nests all requests beneath the URL prefix
+ /// [beneath].
+ static shelf.Middleware nestedIn(String beneath) {
+ return (handler) {
+ var pathHandler = PathHandler()..add(beneath, handler);
+ return pathHandler.handler;
+ };
+ }
+
+ /// Routes requests at or under [path] to [handler].
+ ///
+ /// If [path] is a parent or child directory of another path in this handler,
+ /// the longest matching prefix wins.
+ void add(String path, shelf.Handler handler) {
+ var node = _paths;
+ for (var component in p.url.split(path)) {
+ node = node.children.putIfAbsent(component, _Node.new);
+ }
+ node.handler = handler;
+ }
+
+ FutureOr<shelf.Response> _onRequest(shelf.Request request) {
+ shelf.Handler? handler;
+ int? handlerIndex;
+ _Node? node = _paths;
+ var components = p.url.split(request.url.path);
+ for (var i = 0; i < components.length; i++) {
+ node = node!.children[components[i]];
+ if (node == null) break;
+ if (node.handler == null) continue;
+ handler = node.handler;
+ handlerIndex = i;
+ }
+
+ if (handler == null) return shelf.Response.notFound('Not found.');
+
+ return handler(request.change(
+ path: p.url.joinAll(components.take(handlerIndex! + 1))));
+ }
+}
+
+/// A trie node.
+class _Node {
+ shelf.Handler? handler;
+ final children = <String, _Node>{};
+}
diff --git a/pkgs/test/lib/test.dart b/pkgs/test/lib/test.dart
new file mode 100644
index 0000000..7bcc673
--- /dev/null
+++ b/pkgs/test/lib/test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2013, 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.
+
+export 'package:matcher/expect.dart';
+// Deprecated exports not surfaced through focused libraries.
+// ignore: deprecated_member_use
+export 'package:matcher/src/expect/expect.dart' show ErrorFormatter;
+// ignore: deprecated_member_use
+export 'package:matcher/src/expect/expect_async.dart' show expectAsync;
+// ignore: deprecated_member_use
+export 'package:matcher/src/expect/throws_matcher.dart' show Throws, throws;
+// The non-deprecated API (through a deprecated import).
+// ignore: deprecated_member_use
+export 'package:test_core/test_core.dart';
diff --git a/pkgs/test/mono_pkg.yaml b/pkgs/test/mono_pkg.yaml
new file mode 100644
index 0000000..2aaa4f9
--- /dev/null
+++ b/pkgs/test/mono_pkg.yaml
@@ -0,0 +1,51 @@
+# See https://pub.dev/packages/mono_repo
+
+sdk:
+- dev
+- pubspec
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+ sdk:
+ - dev
+ os:
+ - linux
+- unit_test:
+ - command: xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 0
+ - command: xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 1
+ - command: xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 2
+ - command: xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 3
+ - command: xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 4
+ - command: dart test --preset travis --total-shards 5 --shard-index 0
+ os:
+ - windows
+ - osx
+ sdk:
+ - pubspec
+ - command: dart test --preset travis --total-shards 5 --shard-index 1
+ os:
+ - windows
+ - osx
+ sdk:
+ - pubspec
+ - command: dart test --preset travis --total-shards 5 --shard-index 2
+ os:
+ - windows
+ - osx
+ sdk:
+ - pubspec
+ - command: dart test --preset travis --total-shards 5 --shard-index 3
+ os:
+ - windows
+ - osx
+ sdk:
+ - pubspec
+ - command: dart test --preset travis --total-shards 5 --shard-index 4
+ os:
+ - windows
+ - osx
+ sdk:
+ - pubspec
diff --git a/pkgs/test/pubspec.yaml b/pkgs/test/pubspec.yaml
new file mode 100644
index 0000000..0ab65ad
--- /dev/null
+++ b/pkgs/test/pubspec.yaml
@@ -0,0 +1,53 @@
+name: test
+version: 1.25.12
+description: >-
+ A full featured library for writing and running Dart tests across platforms.
+repository: https://github.com/dart-lang/test/tree/master/pkgs/test
+resolution: workspace
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ analyzer: '>=6.0.0 <8.0.0'
+ async: ^2.5.0
+ boolean_selector: ^2.1.0
+ collection: ^1.15.0
+ coverage: ^1.0.1
+ http_multi_server: ^3.0.0
+ io: ^1.0.0
+ js: '>=0.6.4 <0.8.0'
+
+ # Use a tight version constraint to ensure that a constraint on matcher
+ # properly constrains all features it provides.
+ matcher: '>=0.12.16 <0.12.17'
+
+ node_preamble: ^2.0.0
+ package_config: ^2.0.0
+ path: ^1.8.0
+ pool: ^1.5.0
+ shelf: ^1.0.0
+ shelf_packages_handler: ^3.0.0
+ shelf_static: ^1.0.0
+ shelf_web_socket: '>=1.0.0 <3.0.0'
+ source_span: ^1.8.0
+ stack_trace: ^1.10.0
+ stream_channel: ^2.1.0
+
+ # Use an exact version until the test_api and test_core package are stable.
+ test_api: 0.7.4
+ test_core: 0.6.8
+
+ typed_data: ^1.3.0
+ web_socket_channel: '>=2.0.0 <4.0.0'
+ webkit_inspection_protocol: ^1.0.0
+ yaml: ^3.0.0
+
+dev_dependencies:
+ fake_async: ^1.0.0
+ glob: ^2.0.0
+ test_descriptor: ^2.0.0
+ test_process: ^2.0.0
+
+topics:
+ - testing
diff --git a/pkgs/test/test/common.dart b/pkgs/test/test/common.dart
new file mode 100644
index 0000000..1cad23f
--- /dev/null
+++ b/pkgs/test/test/common.dart
@@ -0,0 +1,6 @@
+// Copyright (c) 2020, 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 'package:test/test.dart';
+
+void myTest(String name, void Function() testFn) => test(name, testFn);
diff --git a/pkgs/test/test/io.dart b/pkgs/test/test/io.dart
new file mode 100644
index 0000000..b13f8bd
--- /dev/null
+++ b/pkgs/test/test/io.dart
@@ -0,0 +1,158 @@
+// Copyright (c) 2015, 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:io';
+import 'dart:isolate';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
+
+/// The path to the root directory of the `test` package.
+final Future<String> packageDir =
+ Isolate.resolvePackageUri(Uri(scheme: 'package', path: 'test/'))
+ .then((uri) {
+ var dir = p.dirname(uri!.path);
+ // If it starts with a `/C:` or other drive letter, remove the leading `/`.
+ if (dir[0] == '/' && dir[2] == ':') dir = dir.substring(1);
+ return dir;
+});
+
+/// The path to the `pub` executable in the current Dart SDK.
+final _pubPath = p.absolute(p.join(p.dirname(Platform.resolvedExecutable),
+ Platform.isWindows ? 'pub.bat' : 'pub'));
+
+/// The platform-specific message emitted when a nonexistent file is loaded.
+final String noSuchFileMessage = Platform.isWindows
+ ? 'The system cannot find the file specified.'
+ : 'No such file or directory';
+
+/// An operating system name that's different than the current operating system.
+final otherOS = Platform.isWindows ? 'mac-os' : 'windows';
+
+/// Expects that the entire stdout stream of [test] equals [expected].
+void expectStdoutEquals(TestProcess test, String expected) =>
+ _expectStreamEquals(test.stdoutStream(), expected);
+
+/// Expects that the entire stderr stream of [test] equals [expected].
+void expectStderrEquals(TestProcess test, String expected) =>
+ _expectStreamEquals(test.stderrStream(), expected);
+
+/// Expects that the entirety of the line stream [stream] equals [expected].
+void _expectStreamEquals(Stream<String> stream, String expected) {
+ expect((() async {
+ var lines = await stream.toList();
+ expect(lines.join('\n').trim(), equals(expected.trim()));
+ })(), completes);
+}
+
+/// Returns a [StreamMatcher] that asserts that the stream emits strings
+/// containing each string in [strings] in order.
+///
+/// This expects each string in [strings] to match a different string in the
+/// stream.
+StreamMatcher containsInOrder(Iterable<String> strings) =>
+ emitsInOrder(strings.map((string) => emitsThrough(contains(string))));
+
+/// Lazily compile the test package to kernel and re-use that, initialized with
+/// [precompileTestExecutable].
+String? _testExecutablePath;
+
+/// Must be invoked before any call to [runTests], should be invoked in a top
+/// level `setUpAll` for best caching results.
+Future<void> precompileTestExecutable() async {
+ if (_testExecutablePath != null) {
+ throw StateError('Test executable already precompiled');
+ }
+ var tmpDirectory = await Directory.systemTemp.createTemp('test');
+ var precompiledPath = p.join(tmpDirectory.path, 'test_runner.dill');
+ var result = await Process.run(Platform.executable, [
+ 'compile',
+ 'kernel',
+ p.url.join(await packageDir, 'bin', 'test.dart'),
+ '-o',
+ precompiledPath,
+ ]);
+ if (result.exitCode != 0) {
+ throw StateError(
+ 'Failed to compile test runner:\n${result.stdout}\n${result.stderr}');
+ }
+
+ addTearDown(() async {
+ await tmpDirectory.delete(recursive: true);
+ });
+ _testExecutablePath = precompiledPath;
+}
+
+/// Runs the test executable with the package root set properly.
+///
+/// You must invoke [precompileTestExecutable] before invoking this function.
+Future<TestProcess> runTest(Iterable<String> args,
+ {String? reporter,
+ String? fileReporter,
+ int? concurrency,
+ Map<String, String>? environment,
+ bool forwardStdio = false,
+ String? packageConfig,
+ Iterable<String>? vmArgs}) async {
+ concurrency ??= 1;
+ var testExecutablePath = _testExecutablePath;
+ if (testExecutablePath == null) {
+ throw StateError(
+ 'You must call `precompileTestExecutable` before calling `runTest`');
+ }
+
+ var allArgs = [
+ ...?vmArgs,
+ testExecutablePath,
+ '--concurrency=$concurrency',
+ if (reporter != null) '--reporter=$reporter',
+ if (fileReporter != null) '--file-reporter=$fileReporter',
+ ...args,
+ ];
+
+ environment ??= {};
+ environment.putIfAbsent('_DART_TEST_TESTING', () => 'true');
+
+ return await runDart(allArgs,
+ environment: environment,
+ description: 'dart bin/test.dart',
+ forwardStdio: forwardStdio,
+ packageConfig: packageConfig);
+}
+
+/// Runs Dart.
+///
+/// If [packageConfig] is provided then that is passed for the `--packages`
+/// arg, otherwise the current isolate config is passed.
+Future<TestProcess> runDart(Iterable<String> args,
+ {Map<String, String>? environment,
+ String? description,
+ bool forwardStdio = false,
+ String? packageConfig}) async {
+ var allArgs = <String>[
+ ...Platform.executableArguments.where((arg) =>
+ !arg.startsWith('--package-root=') && !arg.startsWith('--packages=')),
+ '--packages=${packageConfig ?? await Isolate.packageConfig}',
+ ...args
+ ];
+
+ return await TestProcess.start(
+ p.absolute(Platform.resolvedExecutable), allArgs,
+ workingDirectory: d.sandbox,
+ environment: environment,
+ description: description,
+ forwardStdio: forwardStdio);
+}
+
+/// Runs Pub.
+Future<TestProcess> runPub(Iterable<String> args,
+ {Map<String, String>? environment}) {
+ return TestProcess.start(_pubPath, args,
+ workingDirectory: d.sandbox,
+ environment: environment,
+ description: 'pub ${args.first}');
+}
diff --git a/pkgs/test/test/runner/browser/chrome_test.dart b/pkgs/test/test/runner/browser/chrome_test.dart
new file mode 100644
index 0000000..ffec227
--- /dev/null
+++ b/pkgs/test/test/runner/browser/chrome_test.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2015, 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(['chrome'])
+library;
+
+import 'package:test/src/runner/browser/chrome.dart';
+import 'package:test/src/runner/executable_settings.dart';
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+import '../../utils.dart';
+import 'code_server.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('starts Chrome with the given URL', () async {
+ var server = await CodeServer.start();
+
+ server.handleJavaScript('''
+var webSocket = new WebSocket(window.location.href.replace("http://", "ws://"));
+webSocket.addEventListener("open", function() {
+ webSocket.send("loaded!");
+});
+''');
+ var webSocket = server.handleWebSocket();
+
+ var chrome = Chrome(server.url, configuration());
+ addTearDown(() => chrome.close());
+
+ expect(await (await webSocket).stream.first, equals('loaded!'));
+ },
+ // It's not clear why, but this test in particular seems to time out
+ // when run in parallel with many other tests.
+ timeout: const Timeout.factor(2));
+
+ test("a process can be killed synchronously after it's started", () async {
+ var server = await CodeServer.start();
+ var chrome = Chrome(server.url, configuration());
+ await chrome.close();
+ });
+
+ test('reports an error in onExit', () {
+ var chrome = Chrome(Uri.https('dart.dev'), configuration(),
+ settings: ExecutableSettings(
+ linuxExecutable: '_does_not_exist',
+ macOSExecutable: '_does_not_exist',
+ windowsExecutable: '_does_not_exist'));
+ expect(
+ chrome.onExit,
+ throwsA(isApplicationException(
+ startsWith('Failed to run Chrome: $noSuchFileMessage'))));
+ });
+
+ test('can run successful tests', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('can run failing tests', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw TestFailure("oh no"));
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('can override chrome location with CHROME_EXECUTABLE var', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'CHROME_EXECUTABLE': '/some/bad/path'});
+ expect(test.stdout, emitsThrough(contains('Failed to run Chrome:')));
+ await test.shouldExit(1);
+ });
+}
diff --git a/pkgs/test/test/runner/browser/code_server.dart b/pkgs/test/test/runner/browser/code_server.dart
new file mode 100644
index 0000000..1aba5de
--- /dev/null
+++ b/pkgs/test/test/runner/browser/code_server.dart
@@ -0,0 +1,145 @@
+// Copyright (c) 2015, 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:collection';
+
+import 'package:http_multi_server/http_multi_server.dart';
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+import 'package:test/test.dart' show TestFailure, printOnFailure;
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+/// A class that serves Dart and/or JS code and receives WebSocket connections.
+class CodeServer {
+ final _Handler _handler;
+
+ /// The URL of the server (including the port).
+ final Uri url;
+
+ static Future<CodeServer> start() async {
+ var server = await HttpMultiServer.loopback(0);
+ var handler = _Handler(Zone.current.handleUncaughtError);
+ shelf_io.serveRequests(server, (request) {
+ if (request.method == 'GET' && request.url.path == 'favicon.ico') {
+ return shelf.Response.notFound(null);
+ } else {
+ return handler(request);
+ }
+ });
+
+ return CodeServer._(handler, Uri.parse('http://localhost:${server.port}'));
+ }
+
+ CodeServer._(this._handler, this.url);
+
+ /// Sets up a handler for the root of the server, "/", that serves a basic
+ /// HTML page with a script tag that will run [javaScript].
+ void handleJavaScript(String javaScript) {
+ _handler.expect('GET', '/', (_) {
+ return shelf.Response.ok('''
+<!doctype html>
+<html>
+<head>
+ <script src="index.js"></script>
+</head>
+</html>
+''', headers: {'content-type': 'text/html'});
+ });
+
+ _handler.expect('GET', '/index.js', (_) {
+ return shelf.Response.ok(javaScript,
+ headers: {'content-type': 'application/javascript'});
+ });
+ }
+
+ /// Handles a WebSocket connection to the root of the server, and returns a
+ /// future that will complete to the WebSocket.
+ Future<WebSocketChannel> handleWebSocket() {
+ var completer = Completer<WebSocketChannel>();
+ // Note: the WebSocketChannel type below is needed for compatibility with
+ // package:shelf_web_socket v2.
+ _handler.expect('GET', '/', webSocketHandler((WebSocketChannel ws, _) {
+ completer.complete(ws);
+ }));
+ return completer.future;
+ }
+}
+
+/// A [shelf.Handler] that handles requests as specified by [expect].
+class _Handler {
+ /// A callback called whenever an unexpected exception is thrown.
+ ///
+ /// This is used over throwing errors directly since the request handler might
+ /// not be running in the same error zone as the test.
+ final void Function(Object, StackTrace) _onError;
+
+ /// The queue of expected requests to this handler.
+ final _expectations = Queue<_Expectation>();
+
+ /// Creates a new handler that handles requests using handlers provided by
+ /// [expect].
+ _Handler(this._onError);
+
+ /// Expects that a single HTTP request with the given [method] and [path] will
+ /// be made to [this].
+ ///
+ /// The [path] should be root-relative; that is, it should start with "/".
+ ///
+ /// When a matching request is made, [handler] is used to handle that request.
+ ///
+ /// If this is called multiple times, the requests are expected to occur in
+ /// the same order.
+ void expect(String method, String path, shelf.Handler handler) {
+ _expectations.add(_Expectation(method, path, handler));
+ }
+
+ /// The implementation of [shelf.Handler].
+ FutureOr<shelf.Response> call(shelf.Request request) async {
+ const description = 'ShelfTesthandler';
+ var requestInfo = '${request.method} /${request.url}';
+ printOnFailure('[$description] $requestInfo');
+
+ try {
+ if (_expectations.isEmpty) {
+ throw TestFailure(
+ '$description received unexpected request $requestInfo.');
+ }
+
+ var expectation = _expectations.removeFirst();
+ if ((expectation.method != null &&
+ expectation.method != request.method) ||
+ (expectation.path != '/${request.url.path}' &&
+ expectation.path != null)) {
+ var message = '$description received unexpected request $requestInfo.';
+ if (expectation.method != null) {
+ message += '\nExpected ${expectation.method} ${expectation.path}.';
+ }
+ throw TestFailure(message);
+ }
+
+ return await expectation.handler(request);
+ } on shelf.HijackException catch (_) {
+ rethrow;
+ } catch (error, stackTrace) {
+ _onError(error, stackTrace);
+ return shelf.Response.internalServerError(body: '$error');
+ }
+ }
+}
+
+/// A single expectation for an HTTP request sent to a [_Handler].
+class _Expectation {
+ /// The expected request method, or [null] if this allows any requests.
+ final String? method;
+
+ /// The expected request path, or [null] if this allows any requests.
+ final String? path;
+
+ /// The handler to use for requests that match this expectation.
+ final shelf.Handler handler;
+
+ _Expectation(this.method, this.path, this.handler);
+}
diff --git a/pkgs/test/test/runner/browser/compact_reporter_test.dart b/pkgs/test/test/runner/browser/compact_reporter_test.dart
new file mode 100644
index 0000000..753ffa1
--- /dev/null
+++ b/pkgs/test/test/runner/browser/compact_reporter_test.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('prints the platform name when running on multiple platforms', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(
+ ['-p', 'chrome', '-p', 'vm', '-j', '1', 'test.dart'],
+ reporter: 'compact');
+
+ expect(test.stdout, containsInOrder(['[Chrome, Dart2Js]', '[VM, Kernel]']));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+}
diff --git a/pkgs/test/test/runner/browser/expanded_reporter_test.dart b/pkgs/test/test/runner/browser/expanded_reporter_test.dart
new file mode 100644
index 0000000..875aaba
--- /dev/null
+++ b/pkgs/test/test/runner/browser/expanded_reporter_test.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('prints the platform name when running on multiple platforms', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest([
+ '-r', 'expanded', '-p', 'chrome', '-p', 'vm', '-j', '1', //
+ 'test.dart'
+ ]);
+
+ expect(test.stdoutStream(), emitsThrough(contains('[VM, Kernel]')));
+ expect(test.stdout, emitsThrough(contains('[Chrome, Dart2Js]')));
+ await test.shouldExit(0);
+ }, tags: ['chrome']);
+}
diff --git a/pkgs/test/test/runner/browser/firefox_html_test.dart b/pkgs/test/test/runner/browser/firefox_html_test.dart
new file mode 100644
index 0000000..c9a5302
--- /dev/null
+++ b/pkgs/test/test/runner/browser/firefox_html_test.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2015, 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('firefox')
+library;
+
+import 'package:test/src/runner/browser/dom.dart' as dom;
+import 'package:test/test.dart';
+
+void main() {
+ // Regression test for #274. Firefox doesn't compute styles within hidden
+ // iframes (https://bugzilla.mozilla.org/show_bug.cgi?id=548397), so we have
+ // to do some special stuff to make sure tests that care about that work.
+ test('getComputedStyle() works', () {
+ expect(dom.window.getComputedStyle(dom.document.body!), isNotNull);
+ });
+}
diff --git a/pkgs/test/test/runner/browser/firefox_test.dart b/pkgs/test/test/runner/browser/firefox_test.dart
new file mode 100644
index 0000000..864f49c
--- /dev/null
+++ b/pkgs/test/test/runner/browser/firefox_test.dart
@@ -0,0 +1,115 @@
+// Copyright (c) 2015, 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(['firefox'])
+library;
+
+import 'package:test/src/runner/browser/firefox.dart';
+import 'package:test/src/runner/executable_settings.dart';
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+import '../../utils.dart';
+import 'code_server.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('starts Firefox with the given URL', () async {
+ var server = await CodeServer.start();
+
+ server.handleJavaScript('''
+var webSocket = new WebSocket(window.location.href.replace("http://", "ws://"));
+webSocket.addEventListener("open", function() {
+ webSocket.send("loaded!");
+});
+''');
+ var webSocket = server.handleWebSocket();
+
+ var firefox = Firefox(server.url);
+ addTearDown(() => firefox.close());
+
+ expect(await (await webSocket).stream.first, equals('loaded!'));
+ });
+
+ test("a process can be killed synchronously after it's started", () async {
+ var server = await CodeServer.start();
+
+ var firefox = Firefox(server.url);
+ await firefox.close();
+ });
+
+ test('reports an error in onExit', () {
+ var firefox = Firefox(Uri.https('dart.dev'),
+ settings: ExecutableSettings(
+ linuxExecutable: '_does_not_exist',
+ macOSExecutable: '_does_not_exist',
+ windowsExecutable: '_does_not_exist'));
+ expect(
+ firefox.onExit,
+ throwsA(isApplicationException(
+ startsWith('Failed to run Firefox: $noSuchFileMessage'))));
+ });
+
+ test('can run successful tests', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['-p', 'firefox', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('can run failing tests', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw TestFailure("oh no"));
+}
+''').create();
+
+ var test = await runTest(['-p', 'firefox', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('can override firefox location with FIREFOX_EXECUTABLE var', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+ var test = await runTest(['-p', 'firefox', 'test.dart'],
+ environment: {'FIREFOX_EXECUTABLE': '/some/bad/path'});
+ expect(test.stdout, emitsThrough(contains('Failed to run Firefox:')));
+ await test.shouldExit(1);
+ });
+
+ test('not impacted by CHROME_EXECUTABLE var', () async {
+ await d.file('test.dart', '''
+import 'dart:html';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {
+ assert(window.navigator.vendor != 'Google Inc.');
+ });
+}
+''').create();
+ var test = await runTest(['-p', 'firefox', 'test.dart'],
+ environment: {'CHROME_EXECUTABLE': '/some/bad/path'});
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/browser/loader_test.dart b/pkgs/test/test/runner/browser/loader_test.dart
new file mode 100644
index 0000000..17cdb9a
--- /dev/null
+++ b/pkgs/test/test/runner/browser/loader_test.dart
@@ -0,0 +1,181 @@
+// Copyright (c) 2015, 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(['chrome'])
+library;
+
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/src/runner/browser/platform.dart';
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_api/src/backend/test.dart';
+import 'package:test_core/src/runner/hack_register_platform.dart';
+import 'package:test_core/src/runner/loader.dart';
+import 'package:test_core/src/runner/runner_suite.dart';
+import 'package:test_core/src/runner/runner_test.dart';
+import 'package:test_core/src/runner/runtime_selection.dart';
+import 'package:test_core/src/runner/suite.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../utils.dart';
+
+late Loader _loader;
+
+/// A configuration that loads suites on Chrome.
+final _chrome =
+ SuiteConfiguration.runtimes([RuntimeSelection(Runtime.chrome.identifier)]);
+
+void main() {
+ setUp(() async {
+ // The default loader doesn't have the platforms registered.
+ registerPlatformPlugin([
+ Runtime.chrome,
+ ], () => BrowserPlatform.start(root: d.sandbox));
+
+ _loader = Loader();
+
+ await d.file('a_test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ test("failure", () => throw TestFailure('oh no'));
+ test("error", () => throw 'oh no');
+ }
+ ''').create();
+ });
+
+ tearDown(() => _loader.close());
+
+ group('.loadFile()', () {
+ late RunnerSuite suite;
+ setUp(() async {
+ var suites = await _loader
+ .loadFile(p.join(d.sandbox, 'a_test.dart'), _chrome)
+ .toList();
+
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ suite = (await loadSuite.getSuite())!;
+ });
+
+ test('returns a suite with the file path and platform', () {
+ expect(suite.path, equals(p.join(d.sandbox, 'a_test.dart')));
+ expect(suite.platform.runtime, equals(Runtime.chrome));
+ expect(suite.platform.compiler, equals(Runtime.chrome.defaultCompiler));
+ });
+
+ test('returns tests with the correct names', () {
+ expect(suite.group.entries, hasLength(3));
+ expect(suite.group.entries[0].name, equals('success'));
+ expect(suite.group.entries[1].name, equals('failure'));
+ expect(suite.group.entries[2].name, equals('error'));
+ });
+
+ test('can load and run a successful test', () {
+ var liveTest = (suite.group.entries[0] as RunnerTest).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.success)
+ ]);
+ expectErrors(liveTest, []);
+
+ return liveTest.run().whenComplete(() => liveTest.close());
+ });
+
+ test('can load and run a failing test', () {
+ var liveTest = (suite.group.entries[1] as RunnerTest).load(suite);
+ expectSingleFailure(liveTest);
+ return liveTest.run().whenComplete(() => liveTest.close());
+ });
+ });
+
+ test('loads tests that are defined asynchronously', () async {
+ File(p.join(d.sandbox, 'a_test.dart')).writeAsStringSync('''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+Future main() {
+ return Future(() {
+ test("success", () {});
+
+ return Future(() {
+ test("failure", () => throw TestFailure('oh no'));
+
+ return Future(() {
+ test("error", () => throw 'oh no');
+ });
+ });
+ });
+}
+''');
+
+ var suites = await _loader
+ .loadFile(p.join(d.sandbox, 'a_test.dart'), _chrome)
+ .toList();
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ var suite = (await loadSuite.getSuite())!;
+ expect(suite.group.entries, hasLength(3));
+ expect(suite.group.entries[0].name, equals('success'));
+ expect(suite.group.entries[1].name, equals('failure'));
+ expect(suite.group.entries[2].name, equals('error'));
+ });
+
+ test('loads a suite both in the browser and the VM', () async {
+ var path = p.join(d.sandbox, 'a_test.dart');
+
+ var suites = await _loader
+ .loadFile(
+ path,
+ SuiteConfiguration.runtimes([
+ RuntimeSelection(Runtime.vm.identifier),
+ RuntimeSelection(Runtime.chrome.identifier)
+ ]))
+ .asyncMap((loadSuite) => loadSuite.getSuite())
+ .cast<RunnerSuite>()
+ .toList();
+ expect(suites[0].platform.runtime, equals(Runtime.vm));
+ expect(suites[0].platform.compiler, equals(Runtime.vm.defaultCompiler));
+ expect(suites[0].path, equals(path));
+ expect(suites[1].platform.runtime, equals(Runtime.chrome));
+ expect(suites[1].platform.compiler, equals(Runtime.chrome.defaultCompiler));
+ expect(suites[1].path, equals(path));
+
+ for (var suite in suites) {
+ expect(suite.group.entries, hasLength(3));
+ expect(suite.group.entries[0].name, equals('success'));
+ expect(suite.group.entries[1].name, equals('failure'));
+ expect(suite.group.entries[2].name, equals('error'));
+ }
+ });
+
+ test('a print in a loaded file is piped through the LoadSuite', () async {
+ File(p.join(d.sandbox, 'a_test.dart')).writeAsStringSync('''
+void main() {
+ print('print within test');
+}
+''');
+ var suites = await _loader
+ .loadFile(p.join(d.sandbox, 'a_test.dart'), _chrome)
+ .toList();
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+
+ var liveTest = (loadSuite.group.entries.single as Test).load(loadSuite);
+ // Skip the "Compiled" message from dart2js.
+ expect(liveTest.onMessage.skip(1).first.then((message) => message.text),
+ completion(equals('print within test')));
+ await liveTest.run();
+ expectTestPassed(liveTest);
+ });
+}
diff --git a/pkgs/test/test/runner/browser/microsoft_edge_test.dart b/pkgs/test/test/runner/browser/microsoft_edge_test.dart
new file mode 100644
index 0000000..a48bfd2
--- /dev/null
+++ b/pkgs/test/test/runner/browser/microsoft_edge_test.dart
@@ -0,0 +1,91 @@
+// 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.
+
+@TestOn('vm')
+@Tags(['edge'])
+library;
+
+import 'package:test/src/runner/browser/microsoft_edge.dart';
+import 'package:test/src/runner/executable_settings.dart';
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+import '../../utils.dart';
+import 'code_server.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('starts edge with the given URL', () async {
+ var server = await CodeServer.start();
+
+ server.handleJavaScript('''
+var webSocket = new WebSocket(window.location.href.replace("http://", "ws://"));
+webSocket.addEventListener("open", function() {
+ webSocket.send("loaded!");
+});
+''');
+ var webSocket = server.handleWebSocket();
+
+ var edge = MicrosoftEdge(server.url, configuration());
+ addTearDown(() => edge.close());
+
+ expect(await (await webSocket).stream.first, equals('loaded!'));
+ }, timeout: const Timeout.factor(2));
+
+ test('reports an error in onExit', () {
+ var edge = MicrosoftEdge(Uri.parse('https://dart.dev'), configuration(),
+ settings: ExecutableSettings(
+ linuxExecutable: '_does_not_exist',
+ macOSExecutable: '_does_not_exist',
+ windowsExecutable: '_does_not_exist'));
+ expect(
+ edge.onExit,
+ throwsA(isApplicationException(
+ startsWith('Failed to run Edge: $noSuchFileMessage'))));
+ });
+
+ test('can run successful tests', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['-p', 'edge', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('can run failing tests', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw TestFailure("oh no"));
+}
+''').create();
+
+ var test = await runTest(['-p', 'edge', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('can override edge location with MS_EDGE_EXECUTABLE var', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+ var test = await runTest(['-p', 'edge', 'test.dart'],
+ environment: {'MS_EDGE_EXECUTABLE': '/some/bad/path'});
+ expect(test.stdout, emitsThrough(contains('Failed to run Edge:')));
+ await test.shouldExit(1);
+ });
+}
diff --git a/pkgs/test/test/runner/browser/runner_test.dart b/pkgs/test/test/runner/browser/runner_test.dart
new file mode 100644
index 0000000..c55cbb8
--- /dev/null
+++ b/pkgs/test/test/runner/browser/runner_test.dart
@@ -0,0 +1,978 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.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"));
+}
+''';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('fails gracefully if', () {
+ test('a test file fails to compile', () async {
+ await d.file('test.dart', 'invalid Dart file').create();
+ var test = await runTest(['-p', 'chrome', '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);
+ }, tags: 'chrome');
+
+ test('a test file throws', () async {
+ await d.file('test.dart', "void main() => throw 'oh no';").create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": oh no'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test("a test file doesn't have a main defined", () async {
+ await d.file('test.dart', 'void foo() {}').create();
+
+ var test = await runTest(['-p', 'chrome', '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);
+ }, tags: 'chrome', 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', 'chrome', '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);
+ }, tags: 'chrome', 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', 'chrome', '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);
+ }, tags: 'chrome');
+
+ test('a custom HTML file has no script tag', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel="x-dart-test" href="test.dart">
+</head>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": "test.html" must contain '
+ '<script src="packages/test/dart.js"></script>.'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('a custom HTML file has no link', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <script src="packages/test/dart.js"></script>
+</head>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": Expected exactly 1 '
+ '<link rel="x-dart-test"> in test.html, found 0.'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('a custom HTML file has too many links', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel='x-dart-test' href='test.dart'>
+ <link rel='x-dart-test' href='test.dart'>
+ <script src="packages/test/dart.js"></script>
+</head>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": Expected exactly 1 '
+ '<link rel="x-dart-test"> in test.html, found 2.'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('a custom HTML file has no href in the link', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel='x-dart-test'>
+ <script src="packages/test/dart.js"></script>
+</head>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": Expected <link rel="x-dart-test"> in '
+ 'test.html to have an "href" attribute.'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('a custom HTML file has an invalid test URL', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel='x-dart-test' href='wrong.dart'>
+ <script src="packages/test/dart.js"></script>
+</head>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": Failed to load script at '
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test(
+ 'still errors even with a custom HTML template set since it will take precedence',
+ () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel="x-dart-test" href="test.dart">
+</head>
+</html>
+''').create();
+
+ await d
+ .file(
+ 'global_test.yaml',
+ jsonEncode(
+ {'custom_html_template_path': 'html_template.html.tpl'}))
+ .create();
+
+ await d.file('html_template.html.tpl', '''
+<html>
+<head>
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+</head>
+<body>
+ <div id="foo"></div>
+</body>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": "test.html" must contain '
+ '<script src="packages/test/dart.js"></script>.'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ group('with a custom HTML template', () {
+ setUp(() async {
+ await d.file('test.dart', _success).create();
+ await d
+ .file(
+ 'global_test.yaml',
+ jsonEncode(
+ {'custom_html_template_path': 'html_template.html.tpl'}))
+ .create();
+ });
+
+ test('that does not exist', () async {
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": "html_template.html.tpl" does not exist or is not readable'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test("that doesn't contain the {{testScript}} tag", () async {
+ await d.file('html_template.html.tpl', '''
+<html>
+<head>
+ <script src="packages/test/dart.js"></script>
+</head>
+<body>
+ <div id="foo"></div>
+</body>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": "html_template.html.tpl" must contain exactly one {{testScript}} placeholder'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('that contains more than one {{testScript}} tag', () async {
+ await d.file('html_template.html.tpl', '''
+<html>
+<head>
+ {{testScript}}
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+</head>
+<body>
+ <div id="foo"></div>
+</body>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": "html_template.html.tpl" must contain exactly one {{testScript}} placeholder'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('that has no script tag', () async {
+ await d.file('html_template.html.tpl', '''
+<html>
+<head>
+ {{testScript}}
+</head>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": "html_template.html.tpl" must contain '
+ '<script src="packages/test/dart.js"></script>.'
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('that is named like the test file', () async {
+ await d.file('test.html', '''
+<html>
+<head>
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+</head>
+</html>
+''').create();
+
+ await d
+ .file('global_test_2.yaml',
+ jsonEncode({'custom_html_template_path': 'test.html'}))
+ .create();
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test_2.yaml'});
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart": template file "test.html" cannot be named '
+ 'like the test file.'
+ ]));
+ await test.shouldExit(1);
+ });
+ });
+ });
+
+ group('runs successful tests', () {
+ test('on a browser and the VM', () async {
+ await d.file('test.dart', _success).create();
+ var test = await runTest(['-p', 'chrome', '-p', 'vm', 'test.dart']);
+
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('with setUpAll', () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ setUpAll(() => print("in setUpAll"));
+
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+0: (setUpAll)')));
+ expect(test.stdout, emits('in setUpAll'));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('with tearDownAll', () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ tearDownAll(() => print("in tearDownAll"));
+
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: (tearDownAll)')));
+ expect(test.stdout, emits('in tearDownAll'));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ // 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', 'chrome', 'dir/test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ group('with a custom HTML template file', () {
+ group('without a {{testName}} tag', () {
+ setUp(() async {
+ await d
+ .file(
+ 'global_test.yaml',
+ jsonEncode(
+ {'custom_html_template_path': 'html_template.html.tpl'}))
+ .create();
+ await d.file('html_template.html.tpl', '''
+ <html>
+ <head>
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+ </head>
+ <body>
+ <div id="foo"></div>
+ </body>
+ </html>
+ ''').create();
+
+ await d.file('test.dart', '''
+ import 'package:test/src/runner/browser/dom.dart' as dom;
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {
+ expect(dom.document.querySelector('#foo'), isNotNull);
+ });
+ }
+ ''').create();
+ });
+
+ test('on Chrome', () async {
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+
+ group('with a {{testName}} tag', () {
+ setUp(() async {
+ await d
+ .file(
+ 'global_test.yaml',
+ jsonEncode(
+ {'custom_html_template_path': 'html_template.html.tpl'}))
+ .create();
+ await d.file('html_template.html.tpl', '''
+ <html>
+ <head>
+ <title>{{testName}}</title>
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+ </head>
+ <body>
+ <div id="foo"></div>
+ </body>
+ </html>
+ ''').create();
+
+ await d.file('test-with-title.dart', '''
+ import 'package:test/src/runner/browser/dom.dart' as dom;
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {
+ expect(dom.document.querySelector('#foo'), isNotNull);
+ });
+ test("title", () {
+ expect(dom.document.title, 'test-with-title.dart');
+ });
+ }
+ ''').create();
+ });
+
+ test('on Chrome', () async {
+ var test = await runTest(['-p', 'chrome', 'test-with-title.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+ });
+
+ group('with a custom HTML file', () {
+ setUp(() async {
+ await d.file('test.dart', '''
+import 'package:test/src/runner/browser/dom.dart' as dom;
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {
+ expect(dom.document.querySelector('#foo'), isNotNull);
+ });
+}
+''').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel='x-dart-test' href='test.dart'>
+ <script src="packages/test/dart.js"></script>
+</head>
+<body>
+ <div id="foo"></div>
+</body>
+</html>
+''').create();
+ });
+
+ test('on Chrome', () async {
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ // Regression test for https://github.com/dart-lang/test/issues/82.
+ test('ignores irrelevant link tags', () async {
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel='x-dart-test-not'>
+ <link rel='other' href='test.dart'>
+ <link rel='x-dart-test' href='test.dart'>
+ <script src="packages/test/dart.js"></script>
+</head>
+<body>
+ <div id="foo"></div>
+</body>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('takes precedence over provided HTML template', () async {
+ await d
+ .file(
+ 'global_test.yaml',
+ jsonEncode(
+ {'custom_html_template_path': 'html_template.html.tpl'}))
+ .create();
+ await d.file('html_template.html.tpl', '''
+<html>
+<head>
+ {{testScript}}
+ <script src="packages/test/dart.js"></script>
+</head>
+<body>
+ <div id="not-foo"></div>
+</body>
+</html>
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+ });
+
+ group('runs failing tests', () {
+ test('that fail only on the browser', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ test("test", () {
+ if (p.style == p.Style.url) throw TestFailure("oh no");
+ });
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', '-p', 'vm', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1 -1: Some tests failed.')));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('that fail only on the VM', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ test("test", () {
+ if (p.style != p.Style.url) throw TestFailure("oh no");
+ });
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', '-p', 'vm', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1 -1: Some tests failed.')));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ group('with a custom HTML file', () {
+ setUp(() async {
+ await d.file('test.dart', '''
+import 'package:test/src/runner/browser/dom.dart' as dom;
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () {
+ expect(dom.document.querySelector('#foo'), isNull);
+ });
+}
+''').create();
+
+ await d.file('test.html', '''
+<html>
+<head>
+ <link rel='x-dart-test' href='test.dart'>
+ <script src="packages/test/dart.js"></script>
+</head>
+<body>
+ <div id="foo"></div>
+</body>
+</html>
+''').create();
+ });
+
+ test('on Chrome', () async {
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+ });
+ });
+
+ test('the compiler uses colors if the test runner uses colors', () async {
+ await d.file('test.dart', '{').create();
+
+ var test = await runTest(['--color', '-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('\u001b[31m')));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('forwards prints from the browser 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', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsInOrder([emitsThrough('Hello,'), 'world!']));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('dartifies stack traces for JS-compiled tests by default', () async {
+ await d.file('test.dart', _failure).create();
+
+ var test = await runTest(['-p', 'chrome', '--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);
+ }, tags: 'chrome');
+
+ 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', 'chrome', '--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);
+ }, tags: 'chrome');
+
+ test('respects top-level @Timeout declarations', () async {
+ await d.file('test.dart', '''
+@Timeout(const Duration(seconds: 0))
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("timeout", () => Future.delayed(Duration.zero));
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ 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: {"browser": Skip()});
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+0 ~1: All tests skipped.')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('ignores non-matching Skips', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {}, onPlatform: {"vm": Skip()});
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('respects matching Timeouts', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("fail", () async {
+ await Future.delayed(Duration.zero);
+ throw 'oh no';
+ }, onPlatform: {
+ "browser": Timeout(Duration.zero)
+ });
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('ignores non-matching Timeouts', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {}, onPlatform: {
+ "vm": Timeout(Duration(seconds: 0))
+ });
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('applies matching platforms in order', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {}, onPlatform: {
+ "browser": Skip("first"),
+ "browser || windows": Skip("second"),
+ "browser || linux": Skip("third"),
+ "browser || mac-os": Skip("fourth"),
+ "browser || android": Skip("fifth")
+ });
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdoutStream(), neverEmits(contains('Skip: first')));
+ expect(test.stdoutStream(), neverEmits(contains('Skip: second')));
+ expect(test.stdoutStream(), neverEmits(contains('Skip: third')));
+ expect(test.stdoutStream(), neverEmits(contains('Skip: fourth')));
+ expect(test.stdout, emitsThrough(contains('Skip: fifth')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+
+ group('with an @OnPlatform annotation', () {
+ test('respects matching Skips', () async {
+ await d.file('test.dart', '''
+@OnPlatform(const {"browser": const Skip()})
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("fail", () => throw 'oh no');
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('~1: All tests skipped.')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ 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', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('respects matching Timeouts', () async {
+ await d.file('test.dart', '''
+@OnPlatform(const {
+ "browser": const Timeout(const Duration(seconds: 0))
+})
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("fail", () async {
+ await Future.delayed(Duration.zero);
+ throw 'oh no';
+ });
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+
+ test('ignores non-matching Timeouts', () async {
+ await d.file('test.dart', '''
+@OnPlatform(const {
+ "vm": const Timeout(const Duration(seconds: 0))
+})
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+
+ group('deferred loading', () {
+ test('can run browser tests with deferred library imports', () async {
+ await d.file('deferred.dart', '''
+int x = 1;
+''').create();
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+import 'deferred.dart' deferred as d;
+
+void main() {
+ test("success", () async {
+ await d.loadLibrary();
+ expect(d.x, 1);
+ });
+}
+''').create();
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('stack trace mapping works for deferred loaded libraries', () async {
+ await d.file('deferred.dart', '''
+int get x {
+ throw 'Oh no!';
+}
+''').create();
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+import 'deferred.dart' deferred as d;
+
+void main() {
+ test("failure", () async {
+ await d.loadLibrary();
+ expect(d.x, 1);
+ });
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Oh no!',
+ 'deferred.dart',
+ 'test.dart',
+ ]));
+ await test.shouldExit(1);
+ }, tags: 'chrome');
+ });
+}
diff --git a/pkgs/test/test/runner/browser/safari_test.dart b/pkgs/test/test/runner/browser/safari_test.dart
new file mode 100644
index 0000000..28f4022
--- /dev/null
+++ b/pkgs/test/test/runner/browser/safari_test.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2015, 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(['safari'])
+library;
+
+import 'package:test/src/runner/browser/safari.dart';
+import 'package:test/src/runner/executable_settings.dart';
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+import '../../utils.dart';
+import 'code_server.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('starts Safari with the given URL',
+ skip: 'https://github.com/dart-lang/test/issues/1253', () async {
+ var server = await CodeServer.start();
+
+ server.handleJavaScript('''
+var webSocket = new WebSocket(window.location.href.replace("http://", "ws://"));
+webSocket.addEventListener("open", function() {
+ webSocket.send("loaded!");
+});
+''');
+ var webSocket = server.handleWebSocket();
+
+ var safari = Safari(server.url);
+ addTearDown(() => safari.close());
+
+ expect(await (await webSocket).stream.first, equals('loaded!'));
+ });
+
+ test("a process can be killed synchronously after it's started", () async {
+ var server = await CodeServer.start();
+
+ var safari = Safari(server.url);
+ await safari.close();
+ });
+
+ test('reports an error in onExit', () {
+ var safari = Safari(Uri.https('dart.dev'),
+ settings: ExecutableSettings(
+ linuxExecutable: '_does_not_exist',
+ macOSExecutable: '_does_not_exist',
+ windowsExecutable: '_does_not_exist'));
+ expect(
+ safari.onExit,
+ throwsA(isApplicationException(
+ startsWith('Failed to run Safari: $noSuchFileMessage'))));
+ });
+
+ test('can run successful tests',
+ skip: 'https://github.com/dart-lang/test/issues/1253', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['-p', 'safari', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('can run failing tests', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw TestFailure("oh no"));
+}
+''').create();
+
+ var test = await runTest(['-p', 'safari', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('can override safari location with SAFARI_EXECUTABLE var', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+ var test = await runTest(['-p', 'safari', 'test.dart'],
+ environment: {'SAFARI_EXECUTABLE': '/some/bad/path'});
+ expect(test.stdout, emitsThrough(contains('Failed to run Safari:')));
+ await test.shouldExit(1);
+ });
+}
diff --git a/pkgs/test/test/runner/compact_reporter_test.dart b/pkgs/test/test/runner/compact_reporter_test.dart
new file mode 100644
index 0000000..c935e02
--- /dev/null
+++ b/pkgs/test/test/runner/compact_reporter_test.dart
@@ -0,0 +1,550 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('reports when no tests are run', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ var test = await runTest(['test.dart'], reporter: 'compact');
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('runs several successful tests and reports when each completes', () {
+ return _expectReport('''
+ test('success 1', () {});
+ test('success 2', () {});
+ test('success 3', () {});''', '''
+ +0: loading test.dart
+ +0: success 1
+ +1: success 1
+ +1: success 2
+ +2: success 2
+ +2: success 3
+ +3: success 3
+ +3: All tests passed!''');
+ });
+
+ test('runs several failing tests and reports when each fails', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('failure 3', () => throw TestFailure('oh no'));''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:33 main.<fn>
+
+
+ +0 -1: failure 2
+ +0 -2: failure 2 [E]
+ oh no
+ test.dart 7:33 main.<fn>
+
+
+ +0 -2: failure 3
+ +0 -3: failure 3 [E]
+ oh no
+ test.dart 8:33 main.<fn>
+
+
+ +0 -3: Some tests failed.''');
+ });
+
+ test('includes the full stack trace with --verbose-trace', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw "oh no");
+}
+''').create();
+
+ var test =
+ await runTest(['--verbose-trace', 'test.dart'], reporter: 'compact');
+ expect(test.stdout, emitsThrough(contains('dart:async')));
+ await test.shouldExit(1);
+ });
+
+ test('runs failing tests along with successful tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('success 2', () {});''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:33 main.<fn>
+
+
+ +0 -1: success 1
+ +1 -1: success 1
+ +1 -1: failure 2
+ +1 -2: failure 2 [E]
+ oh no
+ test.dart 8:33 main.<fn>
+
+
+ +1 -2: success 2
+ +2 -2: success 2
+ +2 -2: Some tests failed.''');
+ });
+
+ test('gracefully handles multiple test failures in a row', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // errors have been thrown.
+ var completer = Completer();
+ test('failures', () {
+ Future.microtask(() => throw 'first error');
+ Future.microtask(() => throw 'second error');
+ Future.microtask(() => throw 'third error');
+ Future.microtask(completer.complete);
+ });
+ test('wait', () => completer.future);''', '''
+ +0: loading test.dart
+ +0: failures
+ +0 -1: failures [E]
+ first error
+ test.dart 10:34 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async new Future.microtask
+ test.dart 10:18 main.<fn>
+
+ second error
+ test.dart 11:34 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async new Future.microtask
+ test.dart 11:18 main.<fn>
+
+ third error
+ test.dart 12:34 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async new Future.microtask
+ test.dart 12:18 main.<fn>
+
+
+ +0 -1: wait
+ +1 -1: wait
+ +1 -1: Some tests failed.''');
+ });
+
+ test('prints the full test name before an error', () {
+ return _expectReport('''
+ test(
+ 'really gosh dang long test name. Even longer than that. No, yet '
+ 'longer. Even more. We have to get to at least 200 characters. '
+ 'I know that seems like a lot, but I believe in you. A little '
+ 'more... okay, that should do it.',
+ () => throw TestFailure('oh no'));''', '''
+ +0: loading test.dart
+ +0: really ... than that. No, yet longer. Even more. We have to get to at least 200 characters. I know that seems like a lot, but I believe in you. A little more... okay, that should do it.
+ +0 -1: really gosh dang long test name. Even longer than that. No, yet longer. Even more. We have to get to at least 200 characters. I know that seems like a lot, but I believe in you. A little more... okay, that should do it. [E]
+ oh no
+ test.dart 11:18 main.<fn>
+
+
+ +0 -1: Some tests failed.''');
+ });
+
+ group('print:', () {
+ test('handles multiple prints', () {
+ return _expectReport('''
+ test('test', () {
+ print("one");
+ print("two");
+ print("three");
+ print("four");
+ });''', '''
+ +0: loading test.dart
+ +0: test
+ one
+ two
+ three
+ four
+
+ +1: test
+ +1: All tests passed!''');
+ });
+
+ test('handles a print after the test completes', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var testDone = Completer();
+ var waitStarted = Completer();
+ test('test', () {
+ waitStarted.future.then((_) {
+ Future(() => print("one"));
+ Future(() => print("two"));
+ Future(() => print("three"));
+ Future(() => print("four"));
+ Future(testDone.complete);
+ });
+ });
+
+ test('wait', () {
+ waitStarted.complete();
+ return testDone.future;
+ });''', '''
+ +0: loading test.dart
+ +0: test
+ +1: test
+ +1: wait
+ +1: test
+ one
+ two
+ three
+ four
+
+ +2: wait
+ +2: All tests passed!''');
+ });
+
+ test('interleaves prints and errors', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var completer = Completer();
+ test('test', () {
+ scheduleMicrotask(() {
+ print("three");
+ print("four");
+ throw "second error";
+ });
+
+ scheduleMicrotask(() {
+ print("five");
+ print("six");
+ completer.complete();
+ });
+
+ print("one");
+ print("two");
+ throw "first error";
+ });
+
+ test('wait', () => completer.future);''', '''
+ +0: loading test.dart
+ +0: test
+ one
+ two
+
+ +0 -1: test [E]
+ first error
+ test.dart 24:11 main.<fn>
+
+ three
+ four
+ second error
+ test.dart 13:13 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async scheduleMicrotask
+ test.dart 10:11 main.<fn>
+
+ five
+ six
+
+ +0 -1: wait
+ +1 -1: wait
+ +1 -1: Some tests failed.''');
+ });
+
+ test('prints the full test name before a print', () {
+ return _expectReport('''
+ test(
+ 'really gosh dang long test name. Even longer than that. No, yet '
+ 'longer. Even more. We have to get to at least 200 '
+ 'characters. I know that seems like a lot, but I believe in '
+ 'you. A little more... okay, that should do it.',
+ () => print('hello'));''', '''
+ +0: loading test.dart
+ +0: really ... than that. No, yet longer. Even more. We have to get to at least 200 characters. I know that seems like a lot, but I believe in you. A little more... okay, that should do it.
+ +0: really gosh dang long test name. Even longer than that. No, yet longer. Even more. We have to get to at least 200 characters. I know that seems like a lot, but I believe in you. A little more... okay, that should do it.
+ hello
+
+ +1: really ... than that. No, yet longer. Even more. We have to get to at least 200 characters. I know that seems like a lot, but I believe in you. A little more... okay, that should do it.
+ +1: All tests passed!''');
+ });
+
+ test("doesn't print a clock update between two prints", () {
+ return _expectReport('''
+ test('slow', () async {
+ print('hello');
+ await Future.delayed(Duration(seconds: 3));
+ print('goodbye');
+ });''', '''
+ +0: loading test.dart
+ +0: slow
+ hello
+ goodbye
+
+ +1: slow
+ +1: All tests passed!''');
+ });
+ });
+
+ group('skip:', () {
+ test('displays skipped tests separately', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('skip 2', () {}, skip: true);
+ test('skip 3', () {}, skip: true);''', '''
+ +0: loading test.dart
+ +0: skip 1
+ +0 ~1: skip 1
+ +0 ~1: skip 2
+ +0 ~2: skip 2
+ +0 ~2: skip 3
+ +0 ~3: skip 3
+ +0 ~3: All tests skipped.''');
+ });
+
+ test('displays a skipped group', () {
+ return _expectReport('''
+ group('skip', () {
+ test('test 1', () {});
+ test('test 2', () {});
+ test('test 3', () {});
+ }, skip: true);''', '''
+ +0: loading test.dart
+ +0: skip test 1
+ +0 ~1: skip test 1
+ +0 ~1: skip test 2
+ +0 ~2: skip test 2
+ +0 ~2: skip test 3
+ +0 ~3: skip test 3
+ +0 ~3: All tests skipped.''');
+ });
+
+ test('runs skipped tests along with successful tests', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('success 1', () {});
+ test('skip 2', () {}, skip: true);
+ test('success 2', () {});''', '''
+ +0: loading test.dart
+ +0: skip 1
+ +0 ~1: skip 1
+ +0 ~1: success 1
+ +1 ~1: success 1
+ +1 ~1: skip 2
+ +1 ~2: skip 2
+ +1 ~2: success 2
+ +2 ~2: success 2
+ +2 ~2: All tests passed!''');
+ });
+
+ test('runs skipped tests along with successful and failing tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('skip 1', () {}, skip: true);
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('skip 2', () {}, skip: true);
+ test('success 2', () {});''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:35 main.<fn>
+
+
+ +0 -1: skip 1
+ +0 ~1 -1: skip 1
+ +0 ~1 -1: success 1
+ +1 ~1 -1: success 1
+ +1 ~1 -1: failure 2
+ +1 ~1 -2: failure 2 [E]
+ oh no
+ test.dart 9:35 main.<fn>
+
+
+ +1 ~1 -2: skip 2
+ +1 ~2 -2: skip 2
+ +1 ~2 -2: success 2
+ +2 ~2 -2: success 2
+ +2 ~2 -2: Some tests failed.''');
+ });
+
+ test('displays the skip reason if available', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: 'some reason');
+ test('skip 2', () {}, skip: 'or another');''', '''
+ +0: loading test.dart
+ +0: skip 1
+ Skip: some reason
+
+ +0 ~1: skip 1
+ +0 ~1: skip 2
+ Skip: or another
+
+ +0 ~2: skip 2
+ +0 ~2: All tests skipped.''');
+ });
+
+ test('runs skipped tests with --run-skipped', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: 'some reason');
+ test('skip 2', () {}, skip: 'or another');''', '''
+ +0: loading test.dart
+ +0: skip 1
+ +1: skip 1
+ +1: skip 2
+ +2: skip 2
+ +2: All tests passed!''', args: ['--run-skipped']);
+ });
+ });
+
+ test('Directs users to enable stack trace chaining if disabled', () async {
+ await _expectReport(
+ '''test('failure 1', () => throw TestFailure('oh no'));''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:25 main.<fn>
+ +0 -1: Some tests failed.
+ Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
+ For example, 'dart test --chain-stack-traces'.''',
+ chainStackTraces: false);
+ });
+
+ group('gives non-windows users a way to re-run failed tests', () {
+ final executablePath = p.absolute(Platform.resolvedExecutable);
+
+ test('with simple names', () {
+ return _expectReport('''
+ test('failure', () {
+ expect(1, equals(2));
+ });''', '''
+ +0: loading test.dart
+ +0: failure
+ +0 -1: failure [E]
+ Expected: <2>
+ Actual: <1>
+
+ To run this test again: $executablePath test test.dart -p vm --plain-name 'failure'
+
+ +0 -1: Some tests failed.''');
+ });
+
+ test('escapes names containing single quotes', () {
+ return _expectReport('''
+ test("failure with a ' in the name", () {
+ expect(1, equals(2));
+ });''', '''
+ +0: loading test.dart
+ +0: failure with a ' in the name
+ +0 -1: failure with a ' in the name [E]
+ Expected: <2>
+ Actual: <1>
+
+ To run this test again: $executablePath test test.dart -p vm --plain-name 'failure with a '\\'' in the name'
+
+ +0 -1: Some tests failed.''');
+ });
+ }, testOn: '!windows');
+
+ group('gives windows users a way to re-run failed tests', () {
+ final executablePath = p.absolute(Platform.resolvedExecutable);
+
+ test('with simple names', () {
+ return _expectReport('''
+ test('failure', () {
+ expect(1, equals(2));
+ });''', '''
+ +0: loading test.dart
+ +0: failure
+ +0 -1: failure [E]
+ Expected: <2>
+ Actual: <1>
+
+ To run this test again: $executablePath test test.dart -p vm --plain-name "failure"
+
+ +0 -1: Some tests failed.''');
+ });
+
+ test('escapes names containing double quotes', () {
+ return _expectReport('''
+ test('failure with a " in the name', () {
+ expect(1, equals(2));
+ });''', '''
+ +0: loading test.dart
+ +0: failure with a " in the name
+ +0 -1: failure with a " in the name [E]
+ Expected: <2>
+ Actual: <1>
+
+ To run this test again: $executablePath test test.dart -p vm --plain-name "failure with a """ in the name"
+
+ +0 -1: Some tests failed.''');
+ });
+ }, testOn: 'windows');
+}
+
+Future<void> _expectReport(String tests, String expected,
+ {List<String> args = const [], bool chainStackTraces = true}) async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+$tests
+ }
+ ''').create();
+
+ var test = await runTest([
+ 'test.dart',
+ if (chainStackTraces) '--chain-stack-traces',
+ ...args,
+ ], reporter: 'compact');
+ await test.shouldExit();
+
+ var stdoutLines = await test.stdout.rest.toList();
+
+ // Skip the first CR, remove excess trailing whitespace, and trim off
+ // timestamps.
+ String? lastLine;
+ var actual = stdoutLines.skip(1).map((line) {
+ if (line.startsWith(' ') || line.isEmpty) return line.trimRight();
+
+ var trimmed = line.trim().replaceFirst(RegExp('^[0-9]{2}:[0-9]{2} '), '');
+
+ // Trim identical lines so the test isn't dependent on how fast each test
+ // runs.
+ if (trimmed == lastLine) return null;
+ lastLine = trimmed;
+ return trimmed;
+ }).where((line) => line != null);
+
+ // Un-indent the expected string.
+ var indentation = expected.indexOf(RegExp('[^ ]'));
+ var expectedLines = expected.split('\n').map((line) {
+ if (line.isEmpty) return line;
+ return line.substring(indentation);
+ });
+
+ expect(actual, containsAllInOrder(expectedLines));
+}
diff --git a/pkgs/test/test/runner/compiler_runtime_matrix_test.dart b/pkgs/test/test/runner/compiler_runtime_matrix_test.dart
new file mode 100644
index 0000000..74d8802
--- /dev/null
+++ b/pkgs/test/test/runner/compiler_runtime_matrix_test.dart
@@ -0,0 +1,174 @@
+// 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.
+
+@TestOn('vm')
+@Timeout.factor(2)
+library;
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:test_api/backend.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(() async {
+ await precompileTestExecutable();
+ });
+
+ for (var runtime in Runtime.builtIn) {
+ for (var compiler in runtime.supportedCompilers) {
+ // Ignore the platforms we can't run on this OS.
+ if ((runtime == Runtime.edge && !Platform.isWindows) ||
+ (runtime == Runtime.safari && !Platform.isMacOS)) {
+ continue;
+ }
+ String? skipReason;
+ if (runtime == Runtime.safari) {
+ skipReason = 'https://github.com/dart-lang/test/issues/1253';
+ } else if (compiler == Compiler.dart2wasm) {
+ skipReason = 'Wasm tests are experimental and require special setup';
+ } else if ([Runtime.firefox, Runtime.nodeJS].contains(runtime) &&
+ Platform.isWindows) {
+ skipReason = 'https://github.com/dart-lang/test/issues/1942';
+ } else if (runtime == Runtime.firefox && Platform.isMacOS) {
+ skipReason = 'https://github.com/dart-lang/test/pull/2276';
+ }
+ group('--runtime ${runtime.identifier} --compiler ${compiler.identifier}',
+ skip: skipReason, () {
+ final testArgs = [
+ 'test.dart',
+ '-p',
+ runtime.identifier,
+ '-c',
+ compiler.identifier
+ ];
+
+ test('can run passing tests', () async {
+ await d.file('test.dart', _goodTest).create();
+ var test = await runTest(testArgs);
+
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('fails gracefully for invalid code', () async {
+ await d.file('test.dart', _compileErrorTest).create();
+ var test = await runTest(testArgs);
+
+ expect(
+ test.stdout,
+ containsInOrder([
+ "Error: A value of type 'String' can't be assigned to a variable of type 'int'.",
+ "int x = 'hello';",
+ ]));
+
+ await test.shouldExit(1);
+ });
+
+ test('fails gracefully for test failures', () async {
+ await d.file('test.dart', _failingTest).create();
+ var test = await runTest(testArgs);
+
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Expected: <2>',
+ 'Actual: <1>',
+ 'test.dart 5',
+ '+0 -1: Some tests failed.',
+ ]));
+
+ await test.shouldExit(1);
+ });
+
+ test('fails gracefully if a test file throws in main', () async {
+ await d.file('test.dart', _throwingTest).create();
+ var test = await runTest(testArgs);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: [${runtime.name}, ${compiler.name}] loading test.dart [E]',
+ 'Failed to load "test.dart": oh no'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('captures prints', () async {
+ await d.file('test.dart', _testWithPrints).create();
+ var test = await runTest([...testArgs, '-r', 'json']);
+
+ expect(
+ test.stdout,
+ containsInOrder([
+ '"messageType":"print","message":"hello","type":"print"',
+ ]));
+
+ await test.shouldExit(0);
+ });
+
+ if (runtime.isDartVM) {
+ test('forwards stdout/stderr', () async {
+ await d.file('test.dart', _testWithStdOutAndErr).create();
+ var test = await runTest(testArgs, reporter: 'silent');
+
+ expect(test.stdout, emitsThrough('hello'));
+ expect(test.stderr, emits('world'));
+ await test.shouldExit(0);
+ },
+ skip: Platform.isWindows && compiler == Compiler.exe
+ ? 'https://github.com/dart-lang/test/issues/2150'
+ : null);
+ }
+ });
+ }
+ }
+}
+
+final _goodTest = '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+''';
+
+final _failingTest = '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () {
+ expect(1, 2);
+ });
+ }
+''';
+
+final _compileErrorTest = '''
+int x = 'hello';
+
+void main() {}
+''';
+
+final _throwingTest = "void main() => throw 'oh no';";
+
+final _testWithPrints = '''
+import 'package:test/test.dart';
+
+void main() {
+ print('hello');
+ test('success', () {});
+}''';
+
+final _testWithStdOutAndErr = '''
+import 'dart:io';
+import 'package:test/test.dart';
+
+void main() async {
+ stdout.writeln('hello');
+ await stdout.flush();
+ stderr.writeln('world');
+ test('success', () {});
+}''';
diff --git a/pkgs/test/test/runner/compiler_test.dart b/pkgs/test/test/runner/compiler_test.dart
new file mode 100644
index 0000000..5cf7a47
--- /dev/null
+++ b/pkgs/test/test/runner/compiler_test.dart
@@ -0,0 +1,77 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+final _test = '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+''';
+
+void main() {
+ setUpAll(() async {
+ await precompileTestExecutable();
+ await d.file('test.dart', _test).create();
+ });
+
+ group('--compiler', () {
+ test(
+ 'uses the default compiler if none other is specified for the platform',
+ () async {
+ var test =
+ await runTest(['test.dart', '-p', 'chrome,vm', '-c', 'dart2js']);
+
+ expect(test.stdout, emitsThrough(contains('[Chrome, Dart2Js]')));
+ expect(test.stdout, emitsThrough(contains('[VM, Kernel]')));
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('runs all supported compiler and platform combinations', () async {
+ var test = await runTest(
+ ['test.dart', '-p', 'chrome,vm', '-c', 'dart2js,kernel,source']);
+
+ expect(test.stdout, emitsThrough(contains('[Chrome, Dart2Js]')));
+ expect(test.stdout, emitsThrough(contains('[VM, Kernel]')));
+ expect(test.stdout, emitsThrough(contains('[VM, Source]')));
+ expect(test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('supports platform selectors', () async {
+ var test = await runTest(
+ ['test.dart', '-p', 'vm', '-c', 'vm:source,browser:kernel']);
+
+ expect(test.stdout, emitsThrough(contains('[VM, Source]')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test(
+ 'will only run a given test once for each compiler, even if there are '
+ 'multiple matches', () async {
+ var test =
+ await runTest(['test.dart', '-p', 'vm', '-c', 'vm:source,source']);
+
+ expect(test.stdout, emitsThrough(contains('[VM, Source]')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('fails on unknown compilers', () async {
+ var test = await runTest(['test.dart', '-c', 'fake']);
+ expect(test.stderr, emitsThrough(contains('Invalid compiler `fake`')));
+ await test.shouldExit(64);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/compiler_test.dart b/pkgs/test/test/runner/configuration/compiler_test.dart
new file mode 100644
index 0000000..d8f8d33
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/compiler_test.dart
@@ -0,0 +1,75 @@
+// 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.
+
+@TestOn('vm')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('compilers', () {
+ test('uses specified compilers for supporting platforms', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'compilers': ['source']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome,vm', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: [Chrome, Dart2Js]',
+ '+1: [VM, Source]',
+ '+2: All tests passed!',
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('supports platform selectors with compilers', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'compilers': ['vm:source', 'browser:kernel']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome,vm', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: [Chrome, Dart2Js]',
+ '+1: [VM, Source]',
+ '+2: All tests passed!',
+ ]));
+ await test.shouldExit(0);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/configuration_test.dart b/pkgs/test/test/runner/configuration/configuration_test.dart
new file mode 100644
index 0000000..03cc728
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/configuration_test.dart
@@ -0,0 +1,326 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+import 'package:test_core/src/runner/configuration/reporters.dart';
+import 'package:test_core/src/runner/suite.dart';
+import 'package:test_core/src/util/io.dart';
+
+import '../../utils.dart';
+
+void main() {
+ group('merge', () {
+ group('for most fields', () {
+ test('if neither is defined, preserves the default', () {
+ var merged = configuration().merge(configuration());
+ expect(merged.help, isFalse);
+ expect(merged.version, isFalse);
+ expect(merged.pauseAfterLoad, isFalse);
+ expect(merged.debug, isFalse);
+ expect(merged.color, equals(canUseSpecialChars));
+ expect(merged.configurationPath, equals('dart_test.yaml'));
+ expect(merged.reporter, equals(defaultReporter));
+ expect(merged.fileReporters, isEmpty);
+ expect(merged.shardIndex, isNull);
+ expect(merged.totalShards, isNull);
+ expect(merged.testRandomizeOrderingSeed, isNull);
+ expect(merged.testSelections.keys.single, 'test');
+ });
+
+ test("if only the old configuration's is defined, uses it", () {
+ var merged = configuration(
+ help: true,
+ version: true,
+ pauseAfterLoad: true,
+ debug: true,
+ color: true,
+ configurationPath: 'special_test.yaml',
+ reporter: 'json',
+ fileReporters: {'json': 'out.json'},
+ shardIndex: 3,
+ totalShards: 10,
+ testRandomizeOrderingSeed: 123,
+ testSelections: const {
+ 'bar': {TestSelection()}
+ }).merge(configuration());
+
+ expect(merged.help, isTrue);
+ expect(merged.version, isTrue);
+ expect(merged.pauseAfterLoad, isTrue);
+ expect(merged.debug, isTrue);
+ expect(merged.color, isTrue);
+ expect(merged.configurationPath, equals('special_test.yaml'));
+ expect(merged.reporter, equals('json'));
+ expect(merged.fileReporters, equals({'json': 'out.json'}));
+ expect(merged.shardIndex, equals(3));
+ expect(merged.totalShards, equals(10));
+ expect(merged.testRandomizeOrderingSeed, 123);
+ expect(merged.testSelections.keys.single, 'bar');
+ });
+
+ test("if only the new configuration's is defined, uses it", () {
+ var merged = configuration().merge(configuration(
+ help: true,
+ version: true,
+ pauseAfterLoad: true,
+ debug: true,
+ color: true,
+ configurationPath: 'special_test.yaml',
+ reporter: 'json',
+ fileReporters: {'json': 'out.json'},
+ shardIndex: 3,
+ totalShards: 10,
+ testRandomizeOrderingSeed: 123,
+ testSelections: const {
+ 'bar': {TestSelection()}
+ }));
+
+ expect(merged.help, isTrue);
+ expect(merged.version, isTrue);
+ expect(merged.pauseAfterLoad, isTrue);
+ expect(merged.debug, isTrue);
+ expect(merged.color, isTrue);
+ expect(merged.configurationPath, equals('special_test.yaml'));
+ expect(merged.reporter, equals('json'));
+ expect(merged.fileReporters, equals({'json': 'out.json'}));
+ expect(merged.shardIndex, equals(3));
+ expect(merged.totalShards, equals(10));
+ expect(merged.testRandomizeOrderingSeed, 123);
+ expect(merged.testSelections.keys.single, 'bar');
+ });
+
+ test(
+ "if the two configurations conflict, uses the new configuration's "
+ 'values', () {
+ var older = configuration(
+ help: true,
+ version: false,
+ pauseAfterLoad: true,
+ debug: true,
+ color: false,
+ configurationPath: 'special_test.yaml',
+ reporter: 'json',
+ fileReporters: {'json': 'old.json'},
+ shardIndex: 2,
+ totalShards: 4,
+ testRandomizeOrderingSeed: 0,
+ testSelections: const {
+ 'bar': {TestSelection()}
+ });
+ var newer = configuration(
+ help: false,
+ version: true,
+ pauseAfterLoad: false,
+ debug: false,
+ color: true,
+ configurationPath: 'test_special.yaml',
+ reporter: 'compact',
+ fileReporters: {'json': 'new.json'},
+ shardIndex: 3,
+ totalShards: 10,
+ testRandomizeOrderingSeed: 123,
+ testSelections: const {
+ 'blech': {TestSelection()}
+ });
+ var merged = older.merge(newer);
+
+ expect(merged.help, isFalse);
+ expect(merged.version, isTrue);
+ expect(merged.pauseAfterLoad, isFalse);
+ expect(merged.debug, isFalse);
+ expect(merged.color, isTrue);
+ expect(merged.configurationPath, equals('test_special.yaml'));
+ expect(merged.reporter, equals('compact'));
+ expect(merged.fileReporters, equals({'json': 'new.json'}));
+ expect(merged.shardIndex, equals(3));
+ expect(merged.totalShards, equals(10));
+ expect(merged.testRandomizeOrderingSeed, 123);
+ expect(merged.testSelections.keys.single, 'blech');
+ });
+ });
+
+ group('for chosenPresets', () {
+ test('if neither is defined, preserves the default', () {
+ var merged = configuration().merge(configuration());
+ expect(merged.chosenPresets, isEmpty);
+ });
+
+ test("if only the old configuration's is defined, uses it", () {
+ var merged = configuration(chosenPresets: ['baz', 'bang'])
+ .merge(configuration());
+ expect(merged.chosenPresets, equals(['baz', 'bang']));
+ });
+
+ test("if only the new configuration's is defined, uses it", () {
+ var merged = configuration()
+ .merge(configuration(chosenPresets: ['baz', 'bang']));
+ expect(merged.chosenPresets, equals(['baz', 'bang']));
+ });
+
+ test('if both are defined, unions them', () {
+ var merged = configuration(chosenPresets: ['baz', 'bang'])
+ .merge(configuration(chosenPresets: ['qux']));
+ expect(merged.chosenPresets, equals(['baz', 'bang', 'qux']));
+ });
+ });
+
+ group('for presets', () {
+ test('merges each nested configuration', () {
+ var merged = configuration(presets: {
+ 'bang': configuration(pauseAfterLoad: true),
+ 'qux': configuration(color: true)
+ }).merge(configuration(presets: {
+ 'qux': configuration(color: false),
+ 'zap': configuration(help: true)
+ }));
+
+ expect(merged.presets['bang']!.pauseAfterLoad, isTrue);
+ expect(merged.presets['qux']!.color, isFalse);
+ expect(merged.presets['zap']!.help, isTrue);
+ });
+
+ test('automatically resolves a matching chosen preset', () {
+ var config = configuration(
+ presets: {'foo': configuration(color: true)},
+ chosenPresets: ['foo']);
+ expect(config.presets, isEmpty);
+ expect(config.chosenPresets, equals(['foo']));
+ expect(config.knownPresets, equals(['foo']));
+ expect(config.color, isTrue);
+ });
+
+ test('resolves a chosen presets in order', () {
+ var config = configuration(presets: {
+ 'foo': configuration(color: true),
+ 'bar': configuration(color: false)
+ }, chosenPresets: [
+ 'foo',
+ 'bar'
+ ]);
+ expect(config.presets, isEmpty);
+ expect(config.chosenPresets, equals(['foo', 'bar']));
+ expect(config.knownPresets, unorderedEquals(['foo', 'bar']));
+ expect(config.color, isFalse);
+
+ config = configuration(presets: {
+ 'foo': configuration(color: true),
+ 'bar': configuration(color: false)
+ }, chosenPresets: [
+ 'bar',
+ 'foo'
+ ]);
+ expect(config.presets, isEmpty);
+ expect(config.chosenPresets, equals(['bar', 'foo']));
+ expect(config.knownPresets, unorderedEquals(['foo', 'bar']));
+ expect(config.color, isTrue);
+ });
+
+ test('ignores inapplicable chosen presets', () {
+ var config = configuration(presets: {}, chosenPresets: ['baz']);
+ expect(config.presets, isEmpty);
+ expect(config.chosenPresets, equals(['baz']));
+ expect(config.knownPresets, equals(isEmpty));
+ });
+
+ test('resolves presets through merging', () {
+ var config = configuration(presets: {'foo': configuration(color: true)})
+ .merge(configuration(chosenPresets: ['foo']));
+
+ expect(config.presets, isEmpty);
+ expect(config.chosenPresets, equals(['foo']));
+ expect(config.knownPresets, equals(['foo']));
+ expect(config.color, isTrue);
+ });
+
+ test('preserves known presets through merging', () {
+ var config = configuration(
+ presets: {'foo': configuration(color: true)},
+ chosenPresets: ['foo']).merge(configuration());
+
+ expect(config.presets, isEmpty);
+ expect(config.chosenPresets, equals(['foo']));
+ expect(config.knownPresets, equals(['foo']));
+ expect(config.color, isTrue);
+ });
+ });
+
+ group('for include and excludeTags', () {
+ test('if neither is defined, preserves the default', () {
+ var merged = configuration().merge(configuration());
+ expect(merged.includeTags, equals(BooleanSelector.all));
+ expect(merged.excludeTags, equals(BooleanSelector.none));
+ });
+
+ test("if only the old configuration's is defined, uses it", () {
+ var merged = configuration(
+ includeTags: BooleanSelector.parse('foo || bar'),
+ excludeTags: BooleanSelector.parse('baz || bang'))
+ .merge(configuration());
+
+ expect(merged.includeTags, equals(BooleanSelector.parse('foo || bar')));
+ expect(
+ merged.excludeTags, equals(BooleanSelector.parse('baz || bang')));
+ });
+
+ test("if only the configuration's is defined, uses it", () {
+ var merged = configuration().merge(configuration(
+ includeTags: BooleanSelector.parse('foo || bar'),
+ excludeTags: BooleanSelector.parse('baz || bang')));
+
+ expect(merged.includeTags, equals(BooleanSelector.parse('foo || bar')));
+ expect(
+ merged.excludeTags, equals(BooleanSelector.parse('baz || bang')));
+ });
+
+ test('if both are defined, unions or intersects them', () {
+ var older = configuration(
+ includeTags: BooleanSelector.parse('foo || bar'),
+ excludeTags: BooleanSelector.parse('baz || bang'));
+ var newer = configuration(
+ includeTags: BooleanSelector.parse('blip'),
+ excludeTags: BooleanSelector.parse('qux'));
+ var merged = older.merge(newer);
+
+ expect(merged.includeTags,
+ equals(BooleanSelector.parse('(foo || bar) && blip')));
+ expect(merged.excludeTags,
+ equals(BooleanSelector.parse('(baz || bang) || qux')));
+ });
+ });
+
+ group('for globalPatterns', () {
+ test('if neither is defined, preserves the default', () {
+ var merged = configuration().merge(configuration());
+ expect(merged.globalPatterns, isEmpty);
+ });
+
+ test("if only the old configuration's is defined, uses it", () {
+ var merged = configuration(globalPatterns: ['beep', 'boop'])
+ .merge(configuration());
+
+ expect(merged.globalPatterns, equals(['beep', 'boop']));
+ });
+
+ test("if only the new configuration's is defined, uses it", () {
+ var merged = configuration()
+ .merge(configuration(globalPatterns: ['beep', 'boop']));
+
+ expect(merged.globalPatterns, equals(['beep', 'boop']));
+ });
+
+ test('if both are defined, unions them', () {
+ var older = configuration(globalPatterns: ['beep', 'boop']);
+ var newer = configuration(globalPatterns: ['bonk']);
+ var merged = older.merge(newer);
+
+ expect(
+ merged.globalPatterns, unorderedEquals(['beep', 'boop', 'bonk']));
+ });
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/custom_platform_test.dart b/pkgs/test/test/runner/configuration/custom_platform_test.dart
new file mode 100644
index 0000000..ca9ec05
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/custom_platform_test.dart
@@ -0,0 +1,930 @@
+// 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')
+library;
+
+import 'dart:io';
+
+import 'package:test/src/runner/browser/default_settings.dart';
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ setUp(() async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+ });
+
+ group('override_platforms', () {
+ group('can override a browser', () {
+ test('without any changes', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings: {}
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test("that's user-defined", () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings: {}
+
+ override_platforms:
+ chromium:
+ settings: {}
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('with a basename-only executable', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ firefox:
+ settings:
+ executable:
+ linux: firefox
+ mac_os: firefox
+ windows: firefox.exe
+ ''').create();
+
+ var test = await runTest(['-p', 'firefox', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'firefox');
+
+ test('with an absolute-path executable', () async {
+ String path;
+ if (Platform.isLinux) {
+ var process = await TestProcess.start('which', ['google-chrome']);
+ path = await process.stdout.next;
+ await process.shouldExit(0);
+ } else {
+ path = defaultSettings[Runtime.chrome]!.executable;
+ }
+
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable: $path
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('with non-headless mode', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ headless: false
+ ''').create();
+
+ await d.file('test.dart', '''
+ import 'package:test/src/runner/browser/dom.dart' as dom;
+ import 'package:test/test.dart';
+
+ void main() {
+ test("is not headless", () {
+ expect(dom.window.navigator.userAgent,
+ isNot(contains('HeadlessChrome')));
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+
+ test('can override Node.js without any changes', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ node:
+ settings: {}
+ ''').create();
+
+ var test = await runTest(['-p', 'node', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'node');
+
+ group('errors', () {
+ test('rejects a non-map value', () async {
+ await d.file('dart_test.yaml', 'override_platforms: 12').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['override_platforms must be a map.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects a non-string key', () async {
+ await d
+ .file('dart_test.yaml', 'override_platforms: {12: null}')
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Platform identifier must be a string.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects a non-identifier-like key', () async {
+ await d
+ .file('dart_test.yaml', 'override_platforms: {foo bar: null}')
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'Platform identifier must be an (optionally hyphenated) Dart '
+ 'identifier.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects a non-map definition', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome: 12
+ ''').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Platform definition must be a map.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('requires a settings key', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome: {}
+ ''').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Missing required field "settings".', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('settings must be a map', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings: null
+ ''').create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['Must be a map.', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('the overridden platform must exist', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chromium:
+ settings: {}
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Unknown platform "chromium".', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test("uncustomizable platforms can't be overridden", () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ vm:
+ settings: {}
+ ''').create();
+
+ var test = await runTest(['-p', 'vm', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['The "vm" platform can\'t be customized.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ group('when overriding browsers', () {
+ test('executable must be a string or map', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['Must be a map or a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('executable string may not be relative on POSIX', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable: foo/bar
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Linux and Mac OS executables may not be relative paths.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(1);
+ },
+ // We allow relative executables for Windows so that Windows users
+ // can set a global executable without having to explicitly write
+ // `windows:`.
+ testOn: '!windows');
+
+ test('Linux executable must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable:
+ linux: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, containsInOrder(['Must be a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('Linux executable may not be relative', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable:
+ linux: foo/bar
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Linux and Mac OS executables may not be relative paths.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('Mac OS executable must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable:
+ mac_os: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, containsInOrder(['Must be a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('Mac OS executable may not be relative', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable:
+ mac_os: foo/bar
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Linux and Mac OS executables may not be relative paths.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('Windows executable must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable:
+ windows: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, containsInOrder(['Must be a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('executable must exist', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ executable: _does_not_exist
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ contains('Failed to run Chrome: $noSuchFileMessage')));
+ await test.shouldExit(1);
+ });
+
+ test('executable must exist for Node.js', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ node:
+ settings:
+ executable: _does_not_exist
+ ''').create();
+
+ var test = await runTest(['-p', 'node', 'test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ contains('Failed to run Node.js: $noSuchFileMessage')));
+ await test.shouldExit(1);
+ }, tags: 'node');
+
+ test('headless must be a boolean', () async {
+ await d.file('dart_test.yaml', '''
+ override_platforms:
+ chrome:
+ settings:
+ headless: definitely
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ containsInOrder(['Must be a boolean.', '^^^^^^^^^^'])));
+ await test.shouldExit(1);
+ });
+ });
+ });
+ });
+
+ group('define_platforms', () {
+ group('can define a new browser', () {
+ group('without any changes', () {
+ setUp(() async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings: {}
+ ''').create();
+ });
+
+ test('that can be used to run tests', () async {
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('that can be used in platform selectors', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {}, testOn: "chromium");
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+
+ test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ }, tags: 'chrome');
+
+ test('that counts as its parent', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {}, testOn: "chrome");
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+
+ test('with a basename-only executable', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ my-firefox:
+ name: My Firefox
+ extends: firefox
+ settings:
+ executable:
+ linux: firefox
+ mac_os: firefox
+ windows: firefox.exe
+ ''').create();
+
+ var test = await runTest(['-p', 'my-firefox', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'firefox');
+
+ test('with an absolute-path executable', () async {
+ String path;
+ if (Platform.isLinux) {
+ var process = await TestProcess.start('which', ['google-chrome']);
+ path = await process.stdout.next;
+ await process.shouldExit(0);
+ } else {
+ path = defaultSettings[Runtime.chrome]!.executable;
+ }
+
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable: $path
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+
+ group('errors', () {
+ test('rejects a non-map value', () async {
+ await d.file('dart_test.yaml', 'define_platforms: 12').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['define_platforms must be a map.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects a non-string key', () async {
+ await d.file('dart_test.yaml', 'define_platforms: {12: null}').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Platform identifier must be a string.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects a non-identifier-like key', () async {
+ await d
+ .file('dart_test.yaml', 'define_platforms: {foo bar: null}')
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'Platform identifier must be an (optionally hyphenated) Dart '
+ 'identifier.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects a non-map definition', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium: 12
+ ''').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Platform definition must be a map.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('requires a name key', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ extends: chrome
+ settings: {}
+ ''').create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Missing required field "name".', 'extends: chrome']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('name must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: null
+ extends: chrome
+ settings: {}
+ ''').create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['Must be a string.', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('requires an extends key', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ settings: {}
+ ''').create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Missing required field "extends".', 'name: Chromium']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('extends must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: null
+ settings: {}
+ ''').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Platform parent must be a string.', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('extends must be identifier-like', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: foo bar
+ settings: {}
+ ''').create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'Platform parent must be an (optionally hyphenated) Dart '
+ 'identifier.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('requires a settings key', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ ''').create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Missing required field "settings".', 'name: Chromium']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('settings must be a map', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings: null
+ ''').create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['Must be a map.', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('the new platform may not override an existing platform', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chrome:
+ name: Chromium
+ extends: firefox
+ settings: {}
+ ''').create();
+
+ await d.dir('test').create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'The platform "chrome" already exists. Use override_platforms to '
+ 'override it.',
+ '^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('the new platform must extend an existing platform', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: foobar
+ settings: {}
+ ''').create();
+
+ await d.dir('test').create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['Unknown platform.', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test("the new platform can't extend an uncustomizable platform",
+ () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ myvm:
+ name: My VM
+ extends: vm
+ settings: {}
+ ''').create();
+
+ var test = await runTest(['-p', 'myvm', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['The "vm" platform can\'t be customized.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ group('when overriding browsers', () {
+ test('executable must be a string or map', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['Must be a map or a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('executable string may not be relative on POSIX', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable: foo/bar
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Linux and Mac OS executables may not be relative paths.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(1);
+ },
+ // We allow relative executables for Windows so that Windows users
+ // can set a global executable without having to explicitly write
+ // `windows:`.
+ testOn: '!windows');
+
+ test('Linux executable must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable:
+ linux: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, containsInOrder(['Must be a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('Linux executable may not be relative', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable:
+ linux: foo/bar
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Linux and Mac OS executables may not be relative paths.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('Mac OS executable must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable:
+ mac_os: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, containsInOrder(['Must be a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('Mac OS executable may not be relative', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable:
+ mac_os: foo/bar
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Linux and Mac OS executables may not be relative paths.',
+ '^^^^^^^'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('Windows executable must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable:
+ windows: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, containsInOrder(['Must be a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('executable must exist', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ executable: _does_not_exist
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ contains('Failed to run Chrome: $noSuchFileMessage')));
+ await test.shouldExit(1);
+ });
+
+ test('arguments must be a string', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ arguments: 12
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout, containsInOrder(['Must be a string.', '^^']));
+ await test.shouldExit(1);
+ });
+
+ test('arguments must be shell parseable', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ arguments: --foo 'bar
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['Unmatched single quote.', '^^^^^^^^^^']));
+ await test.shouldExit(1);
+ });
+
+ test('headless must be a boolean', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ chromium:
+ name: Chromium
+ extends: chrome
+ settings:
+ headless: definitely
+ ''').create();
+
+ var test = await runTest(['-p', 'chromium', 'test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ containsInOrder(['Must be a boolean.', '^^^^^^^^^^'])));
+ await test.shouldExit(1);
+ });
+
+ test('with an argument that causes the browser to quit', () async {
+ await d.file('dart_test.yaml', '''
+ define_platforms:
+ myfox:
+ name: My Firefox
+ extends: firefox
+ settings:
+ arguments: --version
+ ''').create();
+
+ var test = await runTest(['-p', 'myfox', 'test.dart']);
+ expect(test.stdout,
+ emitsThrough(contains('My Firefox exited before connecting.')));
+ await test.shouldExit(1);
+ }, tags: 'firefox');
+ });
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/duplicate_names_test.dart b/pkgs/test/test/runner/configuration/duplicate_names_test.dart
new file mode 100644
index 0000000..5a02f52
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/duplicate_names_test.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2021, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('duplicate names', () {
+ group('can be disabled for', () {
+ for (var function in ['group', 'test']) {
+ test('${function}s', () async {
+ await d
+ .file('dart_test.yaml',
+ jsonEncode({'allow_duplicate_test_names': false}))
+ .create();
+
+ var testName = 'test';
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ $function("$testName", () {});
+ $function("$testName", () {});
+ }
+ ''').create();
+
+ var test = await runTest([
+ 'test.dart',
+ '--configuration',
+ p.join(d.sandbox, 'dart_test.yaml')
+ ]);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains(
+ 'A test with the name "$testName" was already declared.')));
+
+ await test.shouldExit(1);
+ });
+ }
+ });
+ group('are allowed by default for', () {
+ for (var function in ['group', 'test']) {
+ test('${function}s', () async {
+ var testName = 'test';
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ $function("$testName", () {});
+ $function("$testName", () {});
+
+ // Needed so at least one test runs when testing groups.
+ test('a test', () {
+ expect(true, isTrue);
+ });
+ }
+ ''').create();
+
+ var test = await runTest(
+ ['test.dart'],
+ );
+
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+
+ await test.shouldExit(0);
+ });
+ }
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/global_test.dart b/pkgs/test/test/runner/configuration/global_test.dart
new file mode 100644
index 0000000..98e8741
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/global_test.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('ignores an empty file', () async {
+ await d.file('global_test.yaml', '').create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('uses supported test configuration', () async {
+ await d
+ .file('global_test.yaml', jsonEncode({'verbose_trace': true}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(test.stdout, emitsThrough(contains('dart:async')));
+ await test.shouldExit(1);
+ });
+
+ test('uses supported runner configuration', () async {
+ await d.file('global_test.yaml', jsonEncode({'reporter': 'json'})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(test.stdout, emitsThrough(contains('"testStart"')));
+ await test.shouldExit(0);
+ });
+
+ test('local configuration takes precedence', () async {
+ await d
+ .file('global_test.yaml', jsonEncode({'verbose_trace': true}))
+ .create();
+
+ await d
+ .file('dart_test.yaml', jsonEncode({'verbose_trace': false}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(test.stdout, neverEmits(contains('dart:isolate-patch')));
+ await test.shouldExit(1);
+ });
+
+ group('disallows local-only configuration:', () {
+ for (var field in [
+ 'skip', 'retry', 'test_on', 'paths', 'filename', 'names', 'tags', //
+ 'plain_names', 'include_tags', 'exclude_tags', 'add_tags',
+ 'define_platforms', 'allow_duplicate_test_names',
+ ]) {
+ test('for $field', () async {
+ await d.file('global_test.yaml', jsonEncode({field: null})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart'],
+ environment: {'DART_TEST_CONFIG': 'global_test.yaml'});
+ expect(
+ test.stderr,
+ containsInOrder(
+ ["of global_test.yaml: $field isn't supported here.", '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ }
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/include_test.dart b/pkgs/test/test/runner/configuration/include_test.dart
new file mode 100644
index 0000000..5d064cf
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/include_test.dart
@@ -0,0 +1,170 @@
+// Copyright (c) 2018, 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')
+library;
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_core/src/runner/configuration.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+void main() {
+ test('should merge with a base configuration', () async {
+ await d.dir('repo', [
+ d.file('dart_test_base.yaml', 'filename: "test_*.dart"'),
+ d.dir('pkg', [
+ d.file('dart_test.yaml', '''
+ include: ../dart_test_base.yaml
+ concurrency: 3
+ '''),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ var config = Configuration.load(path);
+ expect(config.filename.pattern, equals('test_*.dart'));
+ expect(config.concurrency, equals(3));
+ });
+
+ test('should merge fields with a base configuration', () async {
+ await d.dir('repo', [
+ d.file('dart_test_base.yaml', '''
+ tags:
+ hello:
+ '''),
+ d.dir('pkg', [
+ d.file('dart_test.yaml', '''
+ include: ../dart_test_base.yaml
+ tags:
+ world:
+ '''),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ var config = Configuration.load(path);
+ expect(config.knownTags, unorderedEquals(['hello', 'world']));
+ });
+
+ test('should allow an included file to include a file', () async {
+ await d.dir('repo', [
+ d.file('dart_test_base_base.yaml', '''
+ tags:
+ tag:
+ '''),
+ d.file('dart_test_base.yaml', '''
+ include: dart_test_base_base.yaml
+ filename: "test_*.dart"
+ '''),
+ d.dir('pkg', [
+ d.file('dart_test.yaml', '''
+ include: ../dart_test_base.yaml
+ concurrency: 3
+ '''),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ var config = Configuration.load(path);
+ expect(config.knownTags, ['tag']);
+ expect(config.filename.pattern, 'test_*.dart');
+ expect(config.concurrency, 3);
+ });
+
+ test('should not allow an include field in a test config context', () async {
+ await d.dir('repo', [
+ d.dir('pkg', [
+ d.file('dart_test.yaml', r'''
+ tags:
+ foo:
+ include: ../dart_test.yaml
+ '''),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ expect(
+ () => Configuration.load(path),
+ throwsA(allOf(
+ isFormatException,
+ predicate((error) =>
+ error.toString().contains("include isn't supported here")))));
+ });
+
+ test('should allow an include field in a runner config context', () async {
+ await d.dir('repo', [
+ d.dir('pkg', [
+ d.file('dart_test.yaml', '''
+ presets:
+ bar:
+ include: other_dart_test.yaml
+ pause_after_load: true
+ '''),
+ d.file('other_dart_test.yaml', 'reporter: expanded'),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ var config = Configuration.load(path);
+ var presetBar = config.presets['bar']!;
+ expect(presetBar.pauseAfterLoad, isTrue);
+ expect(presetBar.reporter, 'expanded');
+ });
+
+ test('local configuration should take precedence after merging', () async {
+ await d.dir('repo', [
+ d.dir('pkg', [
+ d.file('dart_test.yaml', '''
+ include: other_dart_test.yaml
+ concurrency: 5
+ '''),
+ d.file('other_dart_test.yaml', 'concurrency: 10'),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ var config = Configuration.load(path);
+ expect(config.concurrency, 5);
+ });
+
+ group('gracefully handles', () {
+ test('a non-string include field', () async {
+ await d.dir('repo', [
+ d.dir('pkg', [
+ d.file('dart_test.yaml', 'include: 3'),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ expect(() => Configuration.load(path), throwsFormatException);
+ });
+
+ test('a non-existent included file', () async {
+ await d.dir('repo', [
+ d.dir('pkg', [
+ d.file('dart_test.yaml', 'include: other_test.yaml'),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ expect(() => Configuration.load(path), throwsFormatException);
+ });
+
+ test('an include field in a test config context', () async {
+ await d.dir('repo', [
+ d.dir('pkg', [
+ d.file('dart_test.yaml', '''
+ tags:
+ foo:
+ include: ../dart_test.yaml
+ '''),
+ ]),
+ ]).create();
+
+ var path = p.join(d.sandbox, 'repo', 'pkg', 'dart_test.yaml');
+ expect(() => Configuration.load(path), throwsFormatException);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/platform_test.dart b/pkgs/test/test/runner/configuration/platform_test.dart
new file mode 100644
index 0000000..014c0ed
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/platform_test.dart
@@ -0,0 +1,310 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_core/src/util/io.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('on_platform', () {
+ test('applies platform-specific configuration to matching tests', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_platform': {
+ 'chrome': {'timeout': '0s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome,vm', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['-1: [Chrome, Dart2Js] test [E]', '+1 -1: Some tests failed.']));
+ await test.shouldExit(1);
+ }, tags: ['chrome']);
+
+ test('supports platform selectors', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_platform': {
+ 'chrome || vm': {'timeout': '0s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome,vm', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: [Chrome, Dart2Js] test [E]',
+ '-2: [VM, Kernel] test [E]',
+ '-2: Some tests failed.'
+ ]));
+ await test.shouldExit(1);
+ }, tags: ['chrome']);
+
+ group('errors', () {
+ test('rejects an invalid selector type', () async {
+ await d.file('dart_test.yaml', '{"on_platform": {12: null}}').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['on_platform key must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid selector', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_platform': {'foo bar': null}
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'Invalid on_platform key: Expected end of input.',
+ '^^^^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects a selector with an undefined variable', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_platform': {'foo': null}
+ }))
+ .create();
+
+ await d.dir('test').create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['Undefined variable.', '^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid map', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_platform': {'linux': 12}
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['on_platform value must be a map.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid configuration', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_platform': {
+ 'linux': {'timeout': '12p'}
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Invalid timeout: expected unit.', '^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects runner configuration', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_platform': {
+ 'linux': {'filename': '*_blorp'}
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(["filename isn't supported here.", '^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+ });
+
+ group('on_os', () {
+ test('applies OS-specific configuration on a matching OS', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_os': {
+ currentOS.identifier: {'filename': 'test_*.dart'}
+ }
+ }))
+ .create();
+
+ await d.file('foo_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo_test", () {});
+ }
+ ''').create();
+
+ await d.file('test_foo.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test_foo", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['.']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['+0: ./test_foo.dart: test_foo', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't apply OS-specific configuration on a non-matching OS",
+ () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_os': {
+ otherOS: {'filename': 'test_*.dart'}
+ }
+ }))
+ .create();
+
+ await d.file('foo_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo_test", () {});
+ }
+ ''').create();
+
+ await d.file('test_foo.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test_foo", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['.']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['+0: ./foo_test.dart: foo_test', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ group('errors', () {
+ test('rejects an invalid OS type', () async {
+ await d.file('dart_test.yaml', '{"on_os": {12: null}}').create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr, containsInOrder(['on_os key must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an unknown OS name', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_os': {'foo': null}
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Invalid on_os key: No such operating system.', '^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid map', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_os': {'linux': 12}
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr, containsInOrder(['on_os value must be a map.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid configuration', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_os': {
+ 'linux': {'timeout': '12p'}
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Invalid timeout: expected unit.', '^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/presets_test.dart b/pkgs/test/test/runner/configuration/presets_test.dart
new file mode 100644
index 0000000..193f12e
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/presets_test.dart
@@ -0,0 +1,515 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_core/src/util/io.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('presets', () {
+ test("don't do anything by default", () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ await (await runTest(['test.dart'])).shouldExit(0);
+ });
+
+ test('can be selected on the command line', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['-P', 'foo', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['-1: test [E]', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('multiple presets can be selected', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'},
+ 'bar': {
+ 'paths': ['test.dart']
+ }
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['-P', 'foo,bar']);
+ expect(test.stdout,
+ containsInOrder(['-1: test [E]', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('the latter preset takes precedence', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'},
+ 'bar': {'timeout': '30s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ await (await runTest(['-P', 'foo,bar', 'test.dart'])).shouldExit(0);
+
+ var test = await runTest(['-P', 'bar,foo', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['-1: test [E]', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('a preset takes precedence over the base configuration', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'}
+ },
+ 'timeout': '30s'
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['-P', 'foo', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['-1: test [E]', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '30s'}
+ },
+ 'timeout': '00s'
+ }))
+ .create();
+
+ await (await runTest(['-P', 'foo', 'test.dart'])).shouldExit(0);
+ });
+
+ test('a nested preset is activated', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo': {
+ 'presets': {
+ 'bar': {'timeout': '0s'}
+ },
+ },
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () => Future.delayed(Duration.zero), tags: "foo");
+ test("test 2", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['-P', 'bar', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['+0 -1: test 1 [E]', '+1 -1: Some tests failed.']));
+ await test.shouldExit(1);
+
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '30s'}
+ },
+ 'timeout': '00s'
+ }))
+ .create();
+
+ await (await runTest(['-P', 'foo', 'test.dart'])).shouldExit(0);
+ });
+ });
+
+ group('add_presets', () {
+ test('selects a preset', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'}
+ },
+ 'add_presets': ['foo']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout,
+ containsInOrder(['-1: test [E]', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('applies presets in selection order', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'},
+ 'bar': {'timeout': '30s'}
+ },
+ 'add_presets': ['foo', 'bar']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ await (await runTest(['test.dart'])).shouldExit(0);
+
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '0s'},
+ 'bar': {'timeout': '30s'}
+ },
+ 'add_presets': ['bar', 'foo']
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout,
+ containsInOrder(['-1: test [E]', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('allows preset inheritance via add_presets', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {
+ 'add_presets': ['bar']
+ },
+ 'bar': {'timeout': '0s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['-P', 'foo', 'test.dart']);
+ expect(test.stdout,
+ containsInOrder(['+0 -1: test [E]', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('allows circular preset inheritance via add_presets', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {
+ 'add_presets': ['bar']
+ },
+ 'bar': {
+ 'add_presets': ['foo']
+ }
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ await (await runTest(['-P', 'foo', 'test.dart'])).shouldExit(0);
+ });
+ });
+
+ group('errors', () {
+ group('presets', () {
+ test('rejects an invalid preset type', () async {
+ await d.file('dart_test.yaml', '{"presets": {12: null}}').create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['presets key must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid preset name', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {'foo bar': null}
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'presets key must be an (optionally hyphenated) Dart identifier.',
+ '^^^^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid preset map', () async {
+ await d.file('dart_test.yaml', jsonEncode({'presets': 12})).create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['presets must be a map', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid preset configuration', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'presets': {
+ 'foo': {'timeout': '12p'}
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Invalid timeout: expected unit', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects runner configuration in a non-runner context', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo': {
+ 'presets': {
+ 'bar': {'filename': '*_blorp.dart'}
+ }
+ }
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(["filename isn't supported here.", '^^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('fails if an undefined preset is passed', () async {
+ var test = await runTest(['-P', 'foo']);
+ expect(test.stderr, emitsThrough(contains('Undefined preset "foo".')));
+ await test.shouldExit(exit_codes.usage);
+ });
+
+ test('fails if an undefined preset is added', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'add_presets': ['foo', 'bar']
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ emitsThrough(contains('Undefined presets "foo" and "bar".')));
+ await test.shouldExit(exit_codes.usage);
+ });
+
+ test('fails if an undefined preset is added in a nested context',
+ () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'on_os': {
+ currentOS.identifier: {
+ 'add_presets': ['bar']
+ }
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr, emitsThrough(contains('Undefined preset "bar".')));
+ await test.shouldExit(exit_codes.usage);
+ });
+ });
+
+ group('add_presets', () {
+ test('rejects an invalid list type', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'add_presets': 'foo'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['add_presets must be a list', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid preset type', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'add_presets': [12]
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Preset name must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid preset name', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'add_presets': ['foo bar']
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'Preset name must be an (optionally hyphenated) Dart identifier.',
+ '^^^^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/randomize_order_test.dart b/pkgs/test/test/runner/configuration/randomize_order_test.dart
new file mode 100644
index 0000000..690f54b
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/randomize_order_test.dart
@@ -0,0 +1,234 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('shuffles test order when passed a seed', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {});
+ test("test 2", () {});
+ test("test 3", () {});
+ test("test 4", () {});
+ }
+ ''').create();
+
+ // Test with a given seed
+ var test =
+ await runTest(['test.dart', '--test-randomize-ordering-seed=987654']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: test 4',
+ '+1: test 3',
+ '+2: test 1',
+ '+3: test 2',
+ '+4: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+
+ // Do not shuffle when passed 0
+ test = await runTest(['test.dart', '--test-randomize-ordering-seed=0']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: test 1',
+ '+1: test 2',
+ '+2: test 3',
+ '+3: test 4',
+ '+4: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+
+ // Do not shuffle when passed nothing
+ test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: test 1',
+ '+1: test 2',
+ '+2: test 3',
+ '+3: test 4',
+ '+4: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+
+ // Shuffle when passed random
+ test =
+ await runTest(['test.dart', '--test-randomize-ordering-seed=random']);
+ expect(
+ test.stdout,
+ emitsInAnyOrder([
+ contains('Shuffling test order with --test-randomize-ordering-seed'),
+ isNot(contains(
+ 'Shuffling test order with --test-randomize-ordering-seed=0'))
+ ]));
+ await test.shouldExit(0);
+
+ // Doesn't log about shuffling with the json reporter
+ test = await runTest(
+ ['test.dart', '--test-randomize-ordering-seed=random', '-r', 'json']);
+ expect(test.stdout, neverEmits(contains('Shuffling test order')));
+ await test.shouldExit(0);
+ });
+
+ test('test shuffling can be disabled in dart_test.yml', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'doNotShuffle': {'allow_test_randomization': false}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ @Tags(['doNotShuffle'])
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {});
+ test("test 2", () {});
+ test("test 3", () {});
+ test("test 4", () {});
+ }
+ ''').create();
+
+ var test =
+ await runTest(['test.dart', '--test-randomize-ordering-seed=987654']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: test 1',
+ '+1: test 2',
+ '+2: test 3',
+ '+3: test 4',
+ '+4: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('shuffles each suite with the same seed', () async {
+ await d.file('1_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1.1", () {});
+ test("test 1.2", () {});
+ test("test 1.3", () {});
+ }
+ ''').create();
+
+ await d.file('2_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 2.1", () {});
+ test("test 2.2", () {});
+ test("test 2.3", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['.', '--test-randomize-ordering-seed=12345']);
+ expect(
+ test.stdout,
+ emitsInAnyOrder([
+ containsInOrder([
+ './1_test.dart: test 1.2',
+ './1_test.dart: test 1.3',
+ './1_test.dart: test 1.1'
+ ]),
+ containsInOrder([
+ './2_test.dart: test 2.2',
+ './2_test.dart: test 2.3',
+ './2_test.dart: test 2.1'
+ ]),
+ contains('+6: All tests passed!')
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('shuffles groups as well as tests in groups', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("Group 1", () {
+ test("test 1.1", () {});
+ test("test 1.2", () {});
+ test("test 1.3", () {});
+ test("test 1.4", () {});
+ });
+ group("Group 2", () {
+ test("test 2.1", () {});
+ test("test 2.2", () {});
+ test("test 2.3", () {});
+ test("test 2.4", () {});
+ });
+ }
+ ''').create();
+
+ // Test with a given seed
+ var test =
+ await runTest(['test.dart', '--test-randomize-ordering-seed=123']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: Group 2 test 2.4',
+ '+1: Group 2 test 2.2',
+ '+2: Group 2 test 2.1',
+ '+3: Group 2 test 2.3',
+ '+4: Group 1 test 1.4',
+ '+5: Group 1 test 1.2',
+ '+6: Group 1 test 1.1',
+ '+7: Group 1 test 1.3',
+ '+8: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('shuffles nested groups', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("Group 1", () {
+ test("test 1.1", () {});
+ test("test 1.2", () {});
+ group("Group 2", () {
+ test("test 2.3", () {});
+ test("test 2.4", () {});
+ });
+ });
+ }
+ ''').create();
+
+ var test =
+ await runTest(['test.dart', '--test-randomize-ordering-seed=123']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: Group 1 test 1.1',
+ '+1: Group 1 Group 2 test 2.4',
+ '+2: Group 1 Group 2 test 2.3',
+ '+3: Group 1 test 1.2',
+ '+4: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/suite_test.dart b/pkgs/test/test/runner/configuration/suite_test.dart
new file mode 100644
index 0000000..1c2fc62
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/suite_test.dart
@@ -0,0 +1,147 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/platform_selector.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_core/src/runner/compiler_selection.dart';
+import 'package:test_core/src/runner/runtime_selection.dart';
+
+import '../../utils.dart';
+
+void main() {
+ group('merge', () {
+ group('for most fields', () {
+ test('if neither is defined, preserves the default', () {
+ var merged = suiteConfiguration().merge(suiteConfiguration());
+ expect(merged.jsTrace, isFalse);
+ expect(merged.runSkipped, isFalse);
+ expect(merged.precompiledPath, isNull);
+ expect(merged.runtimes, equals([Runtime.vm.identifier]));
+ expect(merged.compilerSelections, isNull);
+ });
+
+ test("if only the old configuration's is defined, uses it", () {
+ var merged = suiteConfiguration(
+ jsTrace: true,
+ runSkipped: true,
+ precompiledPath: '/tmp/js',
+ runtimes: [RuntimeSelection(Runtime.chrome.identifier)],
+ compilerSelections: [CompilerSelection.parse('dart2js')])
+ .merge(suiteConfiguration());
+
+ expect(merged.jsTrace, isTrue);
+ expect(merged.runSkipped, isTrue);
+ expect(merged.precompiledPath, equals('/tmp/js'));
+ expect(merged.runtimes, equals([Runtime.chrome.identifier]));
+ expect(merged.compilerSelections,
+ equals([CompilerSelection.parse('dart2js')]));
+ });
+
+ test("if only the configuration's is defined, uses it", () {
+ var merged = suiteConfiguration().merge(suiteConfiguration(
+ jsTrace: true,
+ runSkipped: true,
+ precompiledPath: '/tmp/js',
+ runtimes: [RuntimeSelection(Runtime.chrome.identifier)],
+ compilerSelections: [CompilerSelection.parse('dart2js')]));
+
+ expect(merged.jsTrace, isTrue);
+ expect(merged.runSkipped, isTrue);
+ expect(merged.precompiledPath, equals('/tmp/js'));
+ expect(merged.runtimes, equals([Runtime.chrome.identifier]));
+ expect(merged.compilerSelections,
+ equals([CompilerSelection.parse('dart2js')]));
+ });
+
+ test(
+ "if the two configurations conflict, uses the configuration's "
+ 'values', () {
+ var older = suiteConfiguration(
+ jsTrace: false,
+ runSkipped: true,
+ precompiledPath: '/tmp/js',
+ runtimes: [RuntimeSelection(Runtime.chrome.identifier)],
+ compilerSelections: [CompilerSelection.parse('dart2js')]);
+ var newer = suiteConfiguration(
+ jsTrace: true,
+ runSkipped: false,
+ precompiledPath: '../js',
+ runtimes: [RuntimeSelection(Runtime.firefox.identifier)],
+ compilerSelections: [CompilerSelection.parse('source')]);
+ var merged = older.merge(newer);
+
+ expect(merged.jsTrace, isTrue);
+ expect(merged.runSkipped, isFalse);
+ expect(merged.precompiledPath, equals('../js'));
+ expect(merged.runtimes, equals([Runtime.firefox.identifier]));
+ expect(merged.compilerSelections,
+ equals([CompilerSelection.parse('source')]));
+ });
+ });
+
+ group('for dart2jsArgs', () {
+ test('if neither is defined, preserves the default', () {
+ var merged = suiteConfiguration().merge(suiteConfiguration());
+ expect(merged.dart2jsArgs, isEmpty);
+ });
+
+ test("if only the old configuration's is defined, uses it", () {
+ var merged = suiteConfiguration(dart2jsArgs: ['--foo', '--bar'])
+ .merge(suiteConfiguration());
+ expect(merged.dart2jsArgs, equals(['--foo', '--bar']));
+ });
+
+ test("if only the configuration's is defined, uses it", () {
+ var merged = suiteConfiguration()
+ .merge(suiteConfiguration(dart2jsArgs: ['--foo', '--bar']));
+ expect(merged.dart2jsArgs, equals(['--foo', '--bar']));
+ });
+
+ test('if both are defined, concatenates them', () {
+ var older = suiteConfiguration(dart2jsArgs: ['--foo', '--bar']);
+ var newer = suiteConfiguration(dart2jsArgs: ['--baz']);
+ var merged = older.merge(newer);
+ expect(merged.dart2jsArgs, equals(['--foo', '--bar', '--baz']));
+ });
+ });
+
+ group('for config maps', () {
+ test('merges each nested configuration', () {
+ var merged = suiteConfiguration(tags: {
+ BooleanSelector.parse('foo'):
+ suiteConfiguration(precompiledPath: 'path/'),
+ BooleanSelector.parse('bar'): suiteConfiguration(jsTrace: true)
+ }, onPlatform: {
+ PlatformSelector.parse('vm'):
+ suiteConfiguration(precompiledPath: 'path/'),
+ PlatformSelector.parse('chrome'): suiteConfiguration(jsTrace: true)
+ }).merge(suiteConfiguration(tags: {
+ BooleanSelector.parse('bar'): suiteConfiguration(jsTrace: false),
+ BooleanSelector.parse('baz'): suiteConfiguration(runSkipped: true)
+ }, onPlatform: {
+ PlatformSelector.parse('chrome'): suiteConfiguration(jsTrace: false),
+ PlatformSelector.parse('firefox'):
+ suiteConfiguration(runSkipped: true)
+ }));
+
+ expect(merged.tags[BooleanSelector.parse('foo')]!.precompiledPath,
+ equals('path/'));
+ expect(merged.tags[BooleanSelector.parse('bar')]!.jsTrace, isFalse);
+ expect(merged.tags[BooleanSelector.parse('baz')]!.runSkipped, isTrue);
+
+ expect(merged.onPlatform[PlatformSelector.parse('vm')]!.precompiledPath,
+ 'path/');
+ expect(merged.onPlatform[PlatformSelector.parse('chrome')]!.jsTrace,
+ isFalse);
+ expect(merged.onPlatform[PlatformSelector.parse('firefox')]!.runSkipped,
+ isTrue);
+ });
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/tags_test.dart b/pkgs/test/test/runner/configuration/tags_test.dart
new file mode 100644
index 0000000..bbdb1d1
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/tags_test.dart
@@ -0,0 +1,401 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('adds the specified tags', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'add_tags': ['foo', 'bar']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['--exclude-tag', 'foo', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+
+ test = await runTest(['--exclude-tag', 'bar', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+
+ test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ group('tags', () {
+ test("doesn't warn for tags that exist in the configuration", () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {'foo': null}
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, neverEmits(contains('Warning: Tags were used')));
+ await test.shouldExit(0);
+ });
+
+ test('applies tag-specific configuration only to matching tests', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo': {'timeout': '0s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () => Future.delayed(Duration.zero), tags: ['foo']);
+ test("test 2", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout,
+ containsInOrder(['-1: test 1 [E]', '+1 -1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('supports tag selectors', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo && bar': {'timeout': '0s'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () => Future.delayed(Duration.zero), tags: ['foo']);
+ test("test 2", () => Future.delayed(Duration.zero), tags: ['bar']);
+ test("test 3", () => Future.delayed(Duration.zero),
+ tags: ['foo', 'bar']);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout,
+ containsInOrder(['+2 -1: test 3 [E]', '+2 -1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('allows tag inheritance via add_tags', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo': null,
+ 'bar': {
+ 'add_tags': ['foo']
+ }
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {}, tags: ['bar']);
+ test("test 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart', '--tags', 'foo']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ // Regression test for #503.
+ test('skips tests whose tags are marked as skip', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo': {'skip': 'some reason'}
+ }
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () => throw 'bad', tags: ['foo']);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout, containsInOrder(['some reason', 'All tests skipped.']));
+ await test.shouldExit(0);
+ });
+ });
+
+ group('include_tags and exclude_tags', () {
+ test('only runs tests with the included tags', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'include_tags': 'foo && bar'}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("zip", () {}, tags: "foo");
+ test("zap", () {}, tags: "bar");
+ test("zop", () {}, tags: ["foo", "bar"]);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout, containsInOrder(['+0: zop', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run tests with the excluded tags", () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'exclude_tags': 'foo && bar'}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("zip", () {}, tags: "foo");
+ test("zap", () {}, tags: "bar");
+ test("zop", () {}, tags: ["foo", "bar"]);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout,
+ containsInOrder(['+0: zip', '+1: zap', '+2: All tests passed!']));
+ await test.shouldExit(0);
+ });
+ });
+
+ group('errors', () {
+ group('tags', () {
+ test('rejects an invalid tag type', () async {
+ await d.file('dart_test.yaml', '{"tags": {12: null}}').create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr, containsInOrder(['tags key must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid tag selector', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {'foo bar': null}
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Invalid tags key: Expected end of input.', '^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid tag map', () async {
+ await d.file('dart_test.yaml', jsonEncode({'tags': 12})).create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['tags must be a map', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid tag configuration', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo': {'timeout': '12p'}
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(['Invalid timeout: expected unit', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects runner configuration', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'tags': {
+ 'foo': {'filename': '*_blorp.dart'}
+ }
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stderr,
+ containsInOrder(["filename isn't supported here.", '^^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('add_tags', () {
+ test('rejects an invalid list type', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'add_tags': 'foo'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['add_tags must be a list', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid tag type', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'add_tags': [12]
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['Tag name must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid tag name', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'add_tags': ['foo bar']
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr,
+ containsInOrder([
+ 'Tag name must be an (optionally hyphenated) Dart identifier.',
+ '^^^^^^^^^'
+ ]));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('include_tags', () {
+ test('rejects an invalid type', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'include_tags': 12}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['include_tags must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid selector', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'include_tags': 'foo bar'}))
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Invalid include_tags: Expected end of input.', '^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('exclude_tags', () {
+ test('rejects an invalid type', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'exclude_tags': 12}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['exclude_tags must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid selector', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'exclude_tags': 'foo bar'}))
+ .create();
+
+ var test = await runTest([]);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Invalid exclude_tags: Expected end of input.', '^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/top_level_error_test.dart b/pkgs/test/test/runner/configuration/top_level_error_test.dart
new file mode 100644
index 0000000..56a717c
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/top_level_error_test.dart
@@ -0,0 +1,417 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('rejects an invalid fold_stack_frames', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'fold_stack_frames': 'flup'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['fold_stack_frames must be a map', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects multiple fold_stack_frames keys', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'fold_stack_frames': {
+ 'except': ['blah'],
+ 'only': ['blah']
+ }
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Can only contain one of "only" or "except".', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects invalid fold_stack_frames keys', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'fold_stack_frames': {'invalid': 'blah'}
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Must be "only" or "except".', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects invalid fold_stack_frames values', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'fold_stack_frames': {'only': 'blah'}
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Folded packages must be strings', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid pause_after_load', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'pause_after_load': 'flup'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['pause_after_load must be a boolean', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid verbose_trace', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'verbose_trace': 'flup'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['verbose_trace must be a boolean', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid chain_stack_traces', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'chain_stack_traces': 'flup'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['chain_stack_traces must be a boolean', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid retry', () async {
+ await d.file('dart_test.yaml', jsonEncode({'retry': 'flup'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['retry must be a non-negative int', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an negative retry values', () async {
+ await d.file('dart_test.yaml', jsonEncode({'retry': -1})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['retry must be a non-negative int', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid js_trace', () async {
+ await d.file('dart_test.yaml', jsonEncode({'js_trace': 'flup'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['js_trace must be a boolean', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ group('reporter', () {
+ test('rejects an invalid type', () async {
+ await d.file('dart_test.yaml', jsonEncode({'reporter': 12})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr, containsInOrder(['reporter must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid name', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'reporter': 'non-existent'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Unknown reporter "non-existent"', '^^^^^^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('file_reporters', () {
+ test('rejects an invalid type', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'file_reporters': 12}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['file_reporters must be a map', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid value type', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'file_reporters': {'json': 12}
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['file_reporters value must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid name', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'file_reporters': {'non-existent': 'out'}
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr,
+ containsInOrder(
+ ['Unknown reporter "non-existent"', '^^^^^^^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ test('rejects an invalid concurrency', () async {
+ await d.file('dart_test.yaml', jsonEncode({'concurrency': 'foo'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['concurrency must be an int', '^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ group('timeout', () {
+ test('rejects an invalid type', () async {
+ await d.file('dart_test.yaml', jsonEncode({'timeout': 12})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr, containsInOrder(['timeout must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid format', () async {
+ await d.file('dart_test.yaml', jsonEncode({'timeout': '12p'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Invalid timeout: expected unit', '^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('names', () {
+ test('rejects an invalid list type', () async {
+ await d.file('dart_test.yaml', jsonEncode({'names': 'vm'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr, containsInOrder(['names must be a list', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid member type', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'names': [12]
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr, containsInOrder(['Names must be strings', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid RegExp', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'names': ['(foo']
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Invalid name: Unterminated group', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('plain_names', () {
+ test('rejects an invalid list type', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'plain_names': 'vm'}))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['plain_names must be a list', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid member type', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'plain_names': [12]
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr, containsInOrder(['Names must be strings', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('platforms', () {
+ test('rejects an invalid list type', () async {
+ await d.file('dart_test.yaml', jsonEncode({'platforms': 'vm'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['platforms must be a list', '^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid member type', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'platforms': [12]
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Platform name must be a string', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid member name', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'platforms': ['foo']
+ }))
+ .create();
+
+ await d.dir('test').create();
+
+ var test = await runTest([]);
+ expect(test.stderr, containsInOrder(['Unknown platform "foo"', '^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('paths', () {
+ test('rejects an invalid list type', () async {
+ await d.file('dart_test.yaml', jsonEncode({'paths': 'test'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr, containsInOrder(['paths must be a list', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid member type', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'paths': [12]
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr, containsInOrder(['Paths must be strings', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an absolute path', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'paths': ['/foo']
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['Paths must be relative.', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid URI', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'paths': [':invalid']
+ }))
+ .create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Invalid path: Invalid empty scheme', '^^^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+
+ group('filename', () {
+ test('rejects an invalid type', () async {
+ await d.file('dart_test.yaml', jsonEncode({'filename': 12})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr, containsInOrder(['filename must be a string.', '^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('rejects an invalid format', () async {
+ await d.file('dart_test.yaml', jsonEncode({'filename': '{foo'})).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stderr,
+ containsInOrder(['Invalid filename: expected ",".', '^^^^^^']));
+ await test.shouldExit(exit_codes.data);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/configuration/top_level_test.dart b/pkgs/test/test/runner/configuration/top_level_test.dart
new file mode 100644
index 0000000..9c749a4
--- /dev/null
+++ b/pkgs/test/test/runner/configuration/top_level_test.dart
@@ -0,0 +1,578 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:async';
+import 'dart:convert';
+
+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';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('ignores an empty file', () async {
+ await d.file('dart_test.yaml', '').create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('loads configuration from the path passed to --configuration', () async {
+ // Make sure dart_test.yaml is ignored.
+ await d.file('dart_test.yaml', jsonEncode({'run_skipped': true})).create();
+
+ await d.file('special_test.yaml', jsonEncode({'skip': true})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () => throw "oh no");
+ }
+ ''').create();
+
+ var test =
+ await runTest(['--configuration', 'special_test.yaml', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests skipped.')));
+ await test.shouldExit(0);
+ });
+
+ test('pauses the test runner after a suite loads with pause_after_load: true',
+ () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'pause_after_load': true}))
+ .create();
+
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ print('loaded test!');
+
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ await expectLater(test.stdout, emitsThrough('loaded test!'));
+ await expectLater(
+ test.stdout,
+ emitsInOrder([
+ '',
+ equalsIgnoringWhitespace('''
+ The test runner is paused. Open the dev console in Chrome and set
+ breakpoints. Once you're finished, return to this terminal and press
+ Enter.
+ ''')
+ ]));
+
+ var nextLineFired = false;
+
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+0: success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+ await expectLater(
+ test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome', onPlatform: const {
+ 'windows': Skip('https://github.com/dart-lang/test/issues/1613')
+ });
+
+ test('runs skipped tests with run_skipped: true', () async {
+ await d.file('dart_test.yaml', jsonEncode({'run_skipped': true})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("skip", () => print("In test!"), skip: true);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('In test!')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('includes the full stack with verbose_trace: true', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'verbose_trace': true}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('dart:async')));
+ await test.shouldExit(1);
+ });
+
+ test('disables stack trace chaining with chain_stack_traces: false',
+ () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'chain_stack_traces': false}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () async{
+ await Future((){});
+ await Future((){});
+ throw "oh no";
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: failure',
+ '+0 -1: failure [E]',
+ 'oh no',
+ 'test.dart 9:15 main.<fn>',
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test("doesn't dartify stack traces for JS-compiled tests with js_trace: true",
+ () async {
+ await d.file('dart_test.yaml', jsonEncode({'js_trace': true})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', '--verbose-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);
+ }, tags: 'chrome');
+
+ test('retries tests with retry: 1', () async {
+ await d.file('dart_test.yaml', jsonEncode({'retry': 1})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+ import 'dart:async';
+
+ var attempt = 0;
+ void main() {
+ test("test", () {
+ attempt++;
+ if(attempt <= 1) {
+ throw 'Failure!';
+ }
+ });
+ }
+
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed')));
+ await test.shouldExit(0);
+ });
+
+ test('skips tests with skip: true', () async {
+ await d.file('dart_test.yaml', jsonEncode({'skip': true})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests skipped.')));
+ await test.shouldExit(0);
+ });
+
+ test('skips tests with skip: reason', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'skip': 'Tests are boring.'}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('Tests are boring.')));
+ expect(test.stdout, emitsThrough(contains('All tests skipped.')));
+ await test.shouldExit(0);
+ });
+
+ group('test_on', () {
+ test('runs tests on a platform matching platform', () async {
+ await d.file('dart_test.yaml', jsonEncode({'test_on': 'vm'})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('warns about the VM when no OSes are supported', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'test_on': 'chrome'}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr,
+ emits(
+ "Warning: this package doesn't support running tests on the Dart "
+ 'VM.'));
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('warns about the OS when some OSes are supported', () async {
+ await d.file('dart_test.yaml', jsonEncode({'test_on': otherOS})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stderr,
+ emits("Warning: this package doesn't support running tests on "
+ '${currentOS.name}.'));
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('warns about browsers in general when no browsers are supported',
+ () async {
+ await d.file('dart_test.yaml', jsonEncode({'test_on': 'vm'})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome', 'test.dart']);
+ expect(
+ test.stderr,
+ emits(
+ "Warning: this package doesn't support running tests on browsers."));
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test(
+ 'warns about specific browsers when specific browsers are '
+ 'supported', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'test_on': 'safari'}))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'chrome,firefox', 'test.dart']);
+ expect(
+ test.stderr,
+ emits("Warning: this package doesn't support running tests on Chrome "
+ 'or Firefox.'));
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+ });
+
+ test('uses the specified reporter', () async {
+ await d.file('dart_test.yaml', jsonEncode({'reporter': 'json'})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('"testStart"')));
+ await test.shouldExit(0);
+ });
+
+ test('uses the specified concurrency', () async {
+ await d.file('dart_test.yaml', jsonEncode({'concurrency': 2})).create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+
+ // We can't reliably test the concurrency, but this at least ensures that
+ // it doesn't fail to parse.
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('uses the specified timeout', () async {
+ await d.file('dart_test.yaml', jsonEncode({'timeout': '0s'})).create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('runs on the specified platforms', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'platforms': ['vm', 'chrome']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout,
+ containsInOrder(['[VM, Kernel] success', '[Chrome, Dart2Js] success']));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('command line args take precedence', () async {
+ await d.file('dart_test.yaml', jsonEncode({'timeout': '0s'})).create();
+
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () => Future.delayed(Duration.zero));
+ }
+ ''').create();
+
+ var test = await runTest(['--timeout=none', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('uses the specified regexp names', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'names': ['z[ia]p', 'a']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("zip", () {});
+ test("zap", () {});
+ test("zop", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, containsInOrder(['+0: zap', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test('uses the specified plain names', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'names': ['z', 'a']
+ }))
+ .create();
+
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("zip", () {});
+ test("zap", () {});
+ test("zop", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, containsInOrder(['+0: zap', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test('uses the specified paths', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'paths': ['zip', 'zap']
+ }))
+ .create();
+
+ await d.dir('zip', [
+ d.file('zip_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''')
+ ]).create();
+
+ await d.dir('zap', [
+ d.file('zip_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ ''')
+ ]).create();
+
+ await d.dir('zop', [
+ d.file('zip_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ ''')
+ ]).create();
+
+ var test = await runTest([]);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('uses the specified filename', () async {
+ await d
+ .file('dart_test.yaml', jsonEncode({'filename': 'test_*.dart'}))
+ .create();
+
+ await d.dir('test', [
+ d.file('test_foo.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("success", () {});
+ }
+ '''),
+ d.file('foo_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ '''),
+ d.file('test_foo.bart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ ''')
+ ]).create();
+
+ var test = await runTest([]);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/coverage_test.dart b/pkgs/test/test/runner/coverage_test.dart
new file mode 100644
index 0000000..b149345
--- /dev/null
+++ b/pkgs/test/test/runner/coverage_test.dart
@@ -0,0 +1,138 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('with the --coverage flag,', () {
+ late Directory coverageDirectory;
+
+ Future<void> validateCoverage(TestProcess test, String coveragePath) async {
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+
+ final coverageFile = File(p.join(coverageDirectory.path, coveragePath));
+ final coverage = await coverageFile.readAsString();
+ final jsonCoverage = json.decode(coverage);
+ expect(jsonCoverage['coverage'], isNotEmpty);
+ }
+
+ setUp(() async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {
+ expect(true, isTrue);
+ });
+ }
+ ''').create();
+
+ coverageDirectory =
+ await Directory.systemTemp.createTemp('test_coverage');
+ });
+
+ tearDown(() async {
+ await coverageDirectory.delete(recursive: true);
+ });
+
+ test('gathers coverage for VM tests', () async {
+ var test =
+ await runTest(['--coverage', coverageDirectory.path, 'test.dart']);
+ await validateCoverage(test, 'test.dart.vm.json');
+ });
+
+ test('gathers coverage for Chrome tests', () async {
+ var test = await runTest(
+ ['--coverage', coverageDirectory.path, 'test.dart', '-p', 'chrome']);
+ await validateCoverage(test, 'test.dart.chrome.json');
+ });
+
+ test(
+ 'gathers coverage for Chrome tests when JS files contain unicode characters',
+ () async {
+ final sourceMapFileContent =
+ '{"version":3,"file":"","sources":[],"names":[],"mappings":""}';
+ final jsContent = '''
+ (function() {
+ '© '
+ window.foo = function foo() {
+ return 'foo';
+ };
+ })({
+
+ '© ': ''
+ });
+ ''';
+ await d.file('file_with_unicode.js', jsContent).create();
+ await d.file('file_with_unicode.js.map', sourceMapFileContent).create();
+
+ await d.file('js_with_unicode_test.dart', '''
+ import 'dart:async';
+
+ import 'package:js/js.dart';
+ import 'package:js/js_util.dart';
+
+ import 'package:test/src/runner/browser/dom.dart' as dom;
+ import 'package:test/test.dart';
+
+ Future<void> loadScript(String src) async {
+ final controller = StreamController();
+ final scriptLoaded = controller.stream.first;
+ final script = dom.createHTMLScriptElement()..src = src;
+ script.addEventListener('load',
+ allowInterop((_) {
+ controller.add('loaded');
+ }));
+ dom.document.body!.appendChild(script);
+ await scriptLoaded.timeout(Duration(seconds: 1));
+ }
+
+ void main() {
+ test("test 1", () async {
+ await loadScript('file_with_unicode.js');
+ expect(getProperty(dom.window, 'foo'), isNotNull);
+ callMethod(dom.window, 'foo', []);
+ expect(true, isTrue);
+ });
+ }
+ ''').create();
+
+ final jsBytes = utf8.encode(jsContent);
+ final jsLatin1 = latin1.decode(jsBytes);
+ final jsUtf8 = utf8.decode(jsBytes);
+ expect(jsLatin1, isNot(jsUtf8),
+ reason: 'test setup: should have decoded differently');
+
+ const functionPattern = 'function foo';
+ expect([jsLatin1, jsUtf8], everyElement(contains(functionPattern)));
+ expect(jsLatin1.indexOf(functionPattern),
+ isNot(jsUtf8.indexOf(functionPattern)),
+ reason:
+ 'test setup: decoding should have shifted the position of the function');
+
+ var test = await runTest([
+ '--coverage',
+ coverageDirectory.path,
+ 'js_with_unicode_test.dart',
+ '-p',
+ 'chrome'
+ ]);
+ await validateCoverage(test, 'js_with_unicode_test.dart.chrome.json');
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/engine_test.dart b/pkgs/test/test/runner/engine_test.dart
new file mode 100644
index 0000000..6b7df23
--- /dev/null
+++ b/pkgs/test/test/runner/engine_test.dart
@@ -0,0 +1,357 @@
+// Copyright (c) 2015, 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:math';
+
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/group.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_core/src/runner/engine.dart';
+
+import '../utils.dart';
+
+void main() {
+ test('runs each test in each suite in order', () async {
+ var testsRun = 0;
+ var tests = declare(() {
+ for (var i = 0; i < 4; i++) {
+ test(
+ 'test ${i + 1}',
+ expectAsync0(() {
+ expect(testsRun, equals(i));
+ testsRun++;
+ }, max: 1));
+ }
+ });
+
+ var engine = Engine.withSuites([
+ runnerSuite(Group.root(tests.take(2))),
+ runnerSuite(Group.root(tests.skip(2)))
+ ]);
+
+ await engine.run();
+ expect(testsRun, equals(4));
+ });
+
+ test('runs tests in a suite added after run() was called', () {
+ var testsRun = 0;
+ var tests = declare(() {
+ for (var i = 0; i < 4; i++) {
+ test(
+ 'test ${i + 1}',
+ expectAsync0(() {
+ expect(testsRun, equals(i));
+ testsRun++;
+ }, max: 1));
+ }
+ });
+
+ var engine = Engine();
+ expect(
+ engine.run().then((_) {
+ expect(testsRun, equals(4));
+ }),
+ completes);
+
+ engine.suiteSink.add(runnerSuite(Group.root(tests)));
+ engine.suiteSink.close();
+ });
+
+ test('returns fail if any test does not complete', () async {
+ var completer = Completer<void>();
+ var engine = declareEngine(() {
+ test('completes', () {});
+ test('does not complete', () async {
+ await completer.future;
+ });
+ });
+ expect(engine.run(), completion(isFalse));
+ await pumpEventQueue();
+ unawaited(engine.close());
+ // We need to complete this so the outer test finishes.
+ completer.complete();
+ });
+
+ test(
+ 'emits each test before it starts running and after the previous test '
+ 'finished', () {
+ var testsRun = 0;
+ var engine = declareEngine(() {
+ for (var i = 0; i < 3; i++) {
+ test('test ${i + 1}', expectAsync0(() => testsRun++, max: 1));
+ }
+ });
+
+ engine.onTestStarted.listen(expectAsync1((liveTest) {
+ // [testsRun] should be one less than the test currently running.
+ expect(liveTest.test.name, equals('test ${testsRun + 1}'));
+
+ // [Engine.onTestStarted] is guaranteed to fire before the first
+ // [LiveTest.onStateChange].
+ expect(liveTest.onStateChange.first,
+ completion(equals(const State(Status.running, Result.success))));
+ }, count: 3, max: 3));
+
+ return engine.run();
+ });
+
+ test('.run() returns true if every test passes', () {
+ var engine = declareEngine(() {
+ for (var i = 0; i < 2; i++) {
+ test('test ${i + 1}', () {});
+ }
+ });
+
+ expect(engine.run(), completion(isTrue));
+ });
+
+ test('.run() returns false if any test fails', () {
+ var engine = declareEngine(() {
+ for (var i = 0; i < 2; i++) {
+ test('test ${i + 1}', () {});
+ }
+ test('failure', () => throw TestFailure('oh no'));
+ });
+
+ expect(engine.run(), completion(isFalse));
+ });
+
+ test('.run() returns false if any test errors', () {
+ var engine = declareEngine(() {
+ for (var i = 0; i < 2; i++) {
+ test('test ${i + 1}', () {});
+ }
+ test('failure', () => throw 'oh no');
+ });
+
+ expect(engine.run(), completion(isFalse));
+ });
+
+ test('.run() does not run more tests after failure for stopOnFirstFailure',
+ () async {
+ var secondTestRan = false;
+ var engine = declareEngine(() {
+ test('failure', () => throw 'oh no');
+ test('subsequent', () {
+ secondTestRan = true;
+ });
+ }, stopOnFirstFailure: true);
+ await expectLater(engine.run(), completion(isFalse));
+ expect(secondTestRan, false);
+ });
+
+ test('.run() may not be called more than once', () {
+ var engine = Engine.withSuites([]);
+ expect(engine.run(), completes);
+ expect(engine.run, throwsStateError);
+ });
+
+ test('runs tearDown after a test times out', () {
+ // Declare this here so the expect is in the context of this test, rather
+ // than the inner test.
+ var secondTestStarted = false;
+ var firstTestFinished = false;
+ var tearDownBody = expectAsync0(() {
+ expect(secondTestStarted, isFalse);
+ expect(firstTestFinished, isFalse);
+ });
+
+ var engine = declareEngine(() {
+ // This ensures that the first test doesn't actually finish until the
+ // second test runs.
+ var firstTestCompleter = Completer<void>();
+
+ group('group', () {
+ tearDown(tearDownBody);
+
+ test('first test', () async {
+ await firstTestCompleter.future;
+ firstTestFinished = true;
+ }, timeout: const Timeout(Duration.zero));
+ });
+
+ test('second test', () {
+ secondTestStarted = true;
+ firstTestCompleter.complete();
+ });
+ });
+
+ expect(engine.run(), completes);
+ });
+
+ group('for a skipped test', () {
+ test("doesn't run the test's body", () async {
+ var bodyRun = false;
+ var engine = declareEngine(() {
+ test('test', () => bodyRun = true, skip: true);
+ });
+
+ await engine.run();
+ expect(bodyRun, isFalse);
+ });
+
+ test("runs the test's body with --run-skipped", () async {
+ var bodyRun = false;
+ var engine = declareEngine(() {
+ test('test', () => bodyRun = true, skip: true);
+ }, runSkipped: true);
+
+ await engine.run();
+ expect(bodyRun, isTrue);
+ });
+
+ test('exposes a LiveTest that emits the correct states', () {
+ var tests = declare(() {
+ test('test', () {}, skip: true);
+ });
+
+ var engine = Engine.withSuites([runnerSuite(Group.root(tests))]);
+
+ engine.onTestStarted.listen(expectAsync1((liveTest) {
+ expect(liveTest, same(engine.liveTests.single));
+ expect(liveTest.test.name, equals(tests.single.name));
+
+ var i = 0;
+ liveTest.onStateChange.listen(expectAsync1((state) {
+ if (i == 0) {
+ expect(state, equals(const State(Status.running, Result.success)));
+ } else if (i == 1) {
+ expect(state, equals(const State(Status.running, Result.skipped)));
+ } else if (i == 2) {
+ expect(state, equals(const State(Status.complete, Result.skipped)));
+ }
+ i++;
+ }, count: 3));
+
+ expect(liveTest.onComplete, completes);
+ }));
+
+ return engine.run();
+ });
+ });
+
+ group('for a skipped group', () {
+ test("doesn't run a test in the group", () async {
+ var bodyRun = false;
+ var engine = declareEngine(() {
+ group('group', () {
+ test('test', () => bodyRun = true);
+ }, skip: true);
+ });
+
+ await engine.run();
+ expect(bodyRun, isFalse);
+ });
+
+ test('runs tests in the group with --run-skipped', () async {
+ var bodyRun = false;
+ var engine = declareEngine(() {
+ group('group', () {
+ test('test', () => bodyRun = true);
+ }, skip: true);
+ }, runSkipped: true);
+
+ await engine.run();
+ expect(bodyRun, isTrue);
+ });
+
+ test('runs tests in the group when they are skip: false', () async {
+ var bodyRun = false;
+ var engine = declareEngine(() {
+ group('group', () {
+ test('test', skip: false, () => bodyRun = true);
+ }, skip: true);
+ });
+
+ await engine.run();
+ expect(bodyRun, isTrue);
+ });
+
+ test('exposes a LiveTest that emits the correct states', () {
+ var entries = declare(() {
+ group('group', () {
+ test('test', () {});
+ }, skip: true);
+ });
+
+ var engine = Engine.withSuites([runnerSuite(Group.root(entries))]);
+
+ engine.onTestStarted.listen(expectAsync1((liveTest) {
+ expect(liveTest, same(engine.liveTests.single));
+ expect(liveTest.test.name, equals('group test'));
+
+ var i = 0;
+ liveTest.onStateChange.listen(expectAsync1((state) {
+ if (i == 0) {
+ expect(state, equals(const State(Status.running, Result.success)));
+ } else if (i == 1) {
+ expect(state, equals(const State(Status.running, Result.skipped)));
+ } else if (i == 2) {
+ expect(state, equals(const State(Status.complete, Result.skipped)));
+ }
+ i++;
+ }, count: 3));
+
+ expect(liveTest.onComplete, completes);
+ }));
+
+ return engine.run();
+ });
+ });
+
+ group('concurrency', () {
+ test('is shared between runner and load suites', () async {
+ for (var concurrency = 1; concurrency < 5; concurrency++) {
+ var testsLoaded = 0;
+ var maxLoadConcurrency = 0;
+ var testsRunning = 0;
+ var maxTestConcurrency = 0;
+ var testCount = concurrency * 2;
+
+ Future<void> updateAndCheckConcurrency(
+ {bool isLoadSuite = false}) async {
+ if (isLoadSuite) {
+ testsLoaded++;
+ maxLoadConcurrency = max(maxLoadConcurrency, testsLoaded);
+ expect(testsLoaded, lessThanOrEqualTo(concurrency));
+ } else {
+ testsRunning++;
+ maxTestConcurrency = max(maxTestConcurrency, testsRunning);
+ expect(testsRunning, lessThanOrEqualTo(concurrency));
+ }
+ // Simulate the test/loading taking some amount of time so that
+ // we actually reach max concurrency.
+ await Future<void>.delayed(const Duration(milliseconds: 100));
+ if (!isLoadSuite) {
+ testsRunning--;
+ testsLoaded--;
+ }
+ }
+
+ var tests = declare(() {
+ for (var i = 0; i < testCount; i++) {
+ test('test ${i + 1}', () async {
+ await updateAndCheckConcurrency();
+ });
+ }
+ });
+ var engine = Engine.withSuites([
+ for (var i = 0; i < testCount; i++)
+ loadSuite('group $i', () async {
+ await updateAndCheckConcurrency(isLoadSuite: true);
+ return runnerSuite(Group.root([tests[i]]));
+ }),
+ ], concurrency: concurrency);
+
+ await engine.run();
+ expect(engine.liveTests.length, testCount);
+
+ // We should reach but not exceed max concurrency
+ expect(maxTestConcurrency, concurrency);
+ expect(maxLoadConcurrency, concurrency);
+ }
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/expanded_reporter_test.dart b/pkgs/test/test/runner/expanded_reporter_test.dart
new file mode 100644
index 0000000..1c11686
--- /dev/null
+++ b/pkgs/test/test/runner/expanded_reporter_test.dart
@@ -0,0 +1,386 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('reports when no tests are run', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('runs several successful tests and reports when each completes', () {
+ return _expectReport('''
+ test('success 1', () {});
+ test('success 2', () {});
+ test('success 3', () {});''', '''
+ +0: loading test.dart
+ +0: success 1
+ +1: success 2
+ +2: success 3
+ +3: All tests passed!''');
+ });
+
+ test('runs several failing tests and reports when each fails', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('failure 3', () => throw TestFailure('oh no'));''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:33 main.<fn>
+
+ +0 -1: failure 2
+ +0 -2: failure 2 [E]
+ oh no
+ test.dart 7:33 main.<fn>
+
+ +0 -2: failure 3
+ +0 -3: failure 3 [E]
+ oh no
+ test.dart 8:33 main.<fn>
+
+ +0 -3: Some tests failed.''');
+ });
+
+ test('includes the full stack trace with --verbose-trace', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw "oh no");
+}
+''').create();
+
+ var test = await runTest(['--verbose-trace', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('dart:async')));
+ await test.shouldExit(1);
+ });
+
+ test('runs failing tests along with successful tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('success 2', () {});''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:33 main.<fn>
+
+ +0 -1: success 1
+ +1 -1: failure 2
+ +1 -2: failure 2 [E]
+ oh no
+ test.dart 8:33 main.<fn>
+
+ +1 -2: success 2
+ +2 -2: Some tests failed.''');
+ });
+
+ test('always prints the full test name', () {
+ return _expectReport('''
+ test(
+ 'really gosh dang long test name. Even longer than that. No, yet '
+ 'longer. A little more... okay, that should do it.',
+ () {});''', '''
+ +0: loading test.dart
+ +0: really gosh dang long test name. Even longer than that. No, yet longer. A little more... okay, that should do it.
+ +1: All tests passed!''');
+ });
+
+ test('gracefully handles multiple test failures in a row', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // errors have been thrown.
+ var completer = Completer();
+ test('failures', () {
+ Future.microtask(() => throw 'first error');
+ Future.microtask(() => throw 'second error');
+ Future.microtask(() => throw 'third error');
+ Future.microtask(completer.complete);
+ });
+ test('wait', () => completer.future);''', '''
+ +0: loading test.dart
+ +0: failures
+ +0 -1: failures [E]
+ first error
+ test.dart 10:34 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async new Future.microtask
+ test.dart 10:18 main.<fn>
+
+ second error
+ test.dart 11:34 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async new Future.microtask
+ test.dart 11:18 main.<fn>
+
+ third error
+ test.dart 12:34 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async new Future.microtask
+ test.dart 12:18 main.<fn>
+
+ +0 -1: wait
+ +1 -1: Some tests failed.''');
+ });
+
+ group('print:', () {
+ test('handles multiple prints', () {
+ return _expectReport('''
+ test('test', () {
+ print("one");
+ print("two");
+ print("three");
+ print("four");
+ });''', '''
+ +0: loading test.dart
+ +0: test
+ one
+ two
+ three
+ four
+ +1: All tests passed!''');
+ });
+
+ test('handles a print after the test completes', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var testDone = Completer();
+ var waitStarted = Completer();
+ test('test', () async {
+ waitStarted.future.then((_) {
+ Future(() => print("one"));
+ Future(() => print("two"));
+ Future(() => print("three"));
+ Future(() => print("four"));
+ Future(testDone.complete);
+ });
+ });
+
+ test('wait', () {
+ waitStarted.complete();
+ return testDone.future;
+ });''', '''
+ +0: loading test.dart
+ +0: test
+ +1: wait
+ +1: test
+ one
+ two
+ three
+ four
+ +2: All tests passed!''');
+ });
+
+ test('interleaves prints and errors', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var completer = Completer();
+ test('test', () {
+ scheduleMicrotask(() {
+ print("three");
+ print("four");
+ throw "second error";
+ });
+
+ scheduleMicrotask(() {
+ print("five");
+ print("six");
+ completer.complete();
+ });
+
+ print("one");
+ print("two");
+ throw "first error";
+ });
+
+ test('wait', () => completer.future);''', '''
+ +0: loading test.dart
+ +0: test
+ one
+ two
+ +0 -1: test [E]
+ first error
+ test.dart 24:11 main.<fn>
+
+ three
+ four
+ second error
+ test.dart 13:13 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async scheduleMicrotask
+ test.dart 10:11 main.<fn>
+
+ five
+ six
+ +0 -1: wait
+ +1 -1: Some tests failed.''');
+ });
+ });
+
+ group('skip:', () {
+ test('displays skipped tests separately', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('skip 2', () {}, skip: true);
+ test('skip 3', () {}, skip: true);''', '''
+ +0: loading test.dart
+ +0: skip 1
+ +0 ~1: skip 2
+ +0 ~2: skip 3
+ +0 ~3: All tests skipped.''');
+ });
+
+ test('displays a skipped group', () {
+ return _expectReport('''
+ group('skip', () {
+ test('test 1', () {});
+ test('test 2', () {});
+ test('test 3', () {});
+ }, skip: true);''', '''
+ +0: loading test.dart
+ +0: skip test 1
+ +0 ~1: skip test 2
+ +0 ~2: skip test 3
+ +0 ~3: All tests skipped.''');
+ });
+
+ test('runs skipped tests along with successful tests', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('success 1', () {});
+ test('skip 2', () {}, skip: true);
+ test('success 2', () {});''', '''
+ +0: loading test.dart
+ +0: skip 1
+ +0 ~1: success 1
+ +1 ~1: skip 2
+ +1 ~2: success 2
+ +2 ~2: All tests passed!''');
+ });
+
+ test('runs skipped tests along with successful and failing tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('skip 1', () {}, skip: true);
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('skip 2', () {}, skip: true);
+ test('success 2', () {});''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:35 main.<fn>
+
+ +0 -1: skip 1
+ +0 ~1 -1: success 1
+ +1 ~1 -1: failure 2
+ +1 ~1 -2: failure 2 [E]
+ oh no
+ test.dart 9:35 main.<fn>
+
+ +1 ~1 -2: skip 2
+ +1 ~2 -2: success 2
+ +2 ~2 -2: Some tests failed.''');
+ });
+
+ test('displays the skip reason if available', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: 'some reason');
+ test('skip 2', () {}, skip: 'or another');''', '''
+ +0: loading test.dart
+ +0: skip 1
+ Skip: some reason
+ +0 ~1: skip 2
+ Skip: or another
+ +0 ~2: All tests skipped.''');
+ });
+
+ test('runs skipped tests with --run-skipped', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: 'some reason');
+ test('skip 2', () {}, skip: 'or another');''', '''
+ +0: loading test.dart
+ +0: skip 1
+ +1: skip 2
+ +2: All tests passed!''', args: ['--run-skipped']);
+ });
+ });
+
+ test('Directs users to enable stack trace chaining if disabled', () async {
+ await _expectReport(
+ '''test('failure 1', () => throw TestFailure('oh no'));''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:25 main.<fn>
+
+ +0 -1: Some tests failed.
+
+ Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
+ For example, 'dart test --chain-stack-traces'.''',
+ chainStackTraces: false);
+ });
+}
+
+Future<void> _expectReport(String tests, String expected,
+ {List<String> args = const [], bool chainStackTraces = true}) async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+$tests
+ }
+ ''').create();
+
+ var test = await runTest([
+ 'test.dart',
+ if (chainStackTraces) '--chain-stack-traces',
+ ...args,
+ ]);
+ await test.shouldExit();
+
+ var stdoutLines = await test.stdoutStream().toList();
+
+ // Remove excess trailing whitespace and trim off timestamps.
+ var actual = stdoutLines.map((line) {
+ if (line.startsWith(' ') || line.isEmpty) return line.trimRight();
+ return line.trim().replaceFirst(RegExp('^[0-9]{2}:[0-9]{2} '), '');
+ }).join('\n');
+
+ // Un-indent the expected string.
+ var indentation = expected.indexOf(RegExp('[^ ]'));
+ expected = expected.split('\n').map((line) {
+ if (line.isEmpty) return line;
+ return line.substring(indentation);
+ }).join('\n');
+
+ expect(actual, equals(expected));
+}
diff --git a/pkgs/test/test/runner/failures_only_reporter_test.dart b/pkgs/test/test/runner/failures_only_reporter_test.dart
new file mode 100644
index 0000000..3e2690a
--- /dev/null
+++ b/pkgs/test/test/runner/failures_only_reporter_test.dart
@@ -0,0 +1,257 @@
+// Copyright (c) 2024, 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')
+library;
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('reports when no tests are run', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ var test = await runTest(['test.dart'], reporter: 'failures-only');
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('runs several successful tests and reports only at the end', () {
+ return _expectReport('''
+ test('success 1', () {});
+ test('success 2', () {});
+ test('success 3', () {});''', '''
+ +3: All tests passed!''');
+ });
+
+ test('runs several failing tests and reports when each fails', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('failure 3', () => throw TestFailure('oh no'));''', '''
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:33 main.<fn>
+
+ +0 -2: failure 2 [E]
+ oh no
+ test.dart 7:33 main.<fn>
+
+ +0 -3: failure 3 [E]
+ oh no
+ test.dart 8:33 main.<fn>
+
+ +0 -3: Some tests failed.''');
+ });
+
+ test('includes the full stack trace with --verbose-trace', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw "oh no");
+}
+''').create();
+
+ var test = await runTest(['--verbose-trace', 'test.dart'],
+ reporter: 'failures-only');
+ expect(test.stdout, emitsThrough(contains('dart:async')));
+ await test.shouldExit(1);
+ });
+
+ test('reports only failing tests amid successful tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('success 2', () {});''', '''
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:33 main.<fn>
+
+ +1 -2: failure 2 [E]
+ oh no
+ test.dart 8:33 main.<fn>
+
+ +2 -2: Some tests failed.''');
+ });
+
+ group('print:', () {
+ test('handles multiple prints', () {
+ return _expectReport('''
+ test('test', () {
+ print("one");
+ print("two");
+ print("three");
+ print("four");
+ });''', '''
+ +0: test
+ one
+ two
+ three
+ four
+ +1: All tests passed!''');
+ });
+
+ test('handles a print after the test completes', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var testDone = Completer();
+ var waitStarted = Completer();
+ test('test', () async {
+ waitStarted.future.then((_) {
+ Future(() => print("one"));
+ Future(() => print("two"));
+ Future(() => print("three"));
+ Future(() => print("four"));
+ Future(testDone.complete);
+ });
+ });
+
+ test('wait', () {
+ waitStarted.complete();
+ return testDone.future;
+ });''', '''
+ +1: test
+ one
+ two
+ three
+ four
+ +2: All tests passed!''');
+ });
+
+ test('interleaves prints and errors', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var completer = Completer();
+ test('test', () {
+ scheduleMicrotask(() {
+ print("three");
+ print("four");
+ throw "second error";
+ });
+
+ scheduleMicrotask(() {
+ print("five");
+ print("six");
+ completer.complete();
+ });
+
+ print("one");
+ print("two");
+ throw "first error";
+ });
+
+ test('wait', () => completer.future);''', '''
+ +0: test
+ one
+ two
+ +0 -1: test [E]
+ first error
+ test.dart 24:11 main.<fn>
+
+ three
+ four
+ second error
+ test.dart 13:13 main.<fn>.<fn>
+ ===== asynchronous gap ===========================
+ dart:async scheduleMicrotask
+ test.dart 10:11 main.<fn>
+
+ five
+ six
+ +1 -1: Some tests failed.''');
+ });
+ });
+
+ group('skip:', () {
+ test('does not emit for skips', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('skip 2', () {}, skip: true);
+ test('skip 3', () {}, skip: true);''', '''
+ +0 ~3: All tests skipped.''');
+ });
+
+ test('runs skipped tests along with successful and failing tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('skip 1', () {}, skip: true);
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('skip 2', () {}, skip: true);
+ test('success 2', () {});''', '''
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:35 main.<fn>
+
+ +1 ~1 -2: failure 2 [E]
+ oh no
+ test.dart 9:35 main.<fn>
+
+ +2 ~2 -2: Some tests failed.''');
+ });
+ });
+
+ test('Directs users to enable stack trace chaining if disabled', () async {
+ await _expectReport(
+ '''test('failure 1', () => throw TestFailure('oh no'));''', '''
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:25 main.<fn>
+
+ +0 -1: Some tests failed.
+
+ Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
+ For example, 'dart test --chain-stack-traces'.''',
+ chainStackTraces: false);
+ });
+}
+
+Future<void> _expectReport(String tests, String expected,
+ {List<String> args = const [], bool chainStackTraces = true}) async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+$tests
+ }
+ ''').create();
+
+ var test = await runTest([
+ 'test.dart',
+ if (chainStackTraces) '--chain-stack-traces',
+ ...args,
+ ], reporter: 'failures-only');
+ await test.shouldExit();
+
+ var stdoutLines = await test.stdoutStream().toList();
+
+ // Remove excess trailing whitespace.
+ var actual = stdoutLines.map((line) {
+ if (line.startsWith(' ') || line.isEmpty) return line.trimRight();
+ return line.trim();
+ }).join('\n');
+
+ // Un-indent the expected string.
+ var indentation = expected.indexOf(RegExp('[^ ]'));
+ expected = expected.split('\n').map((line) {
+ if (line.isEmpty) return line;
+ return line.substring(indentation);
+ }).join('\n');
+
+ expect(actual, equals(expected));
+}
diff --git a/pkgs/test/test/runner/github_reporter_test.dart b/pkgs/test/test/runner/github_reporter_test.dart
new file mode 100644
index 0000000..db50058
--- /dev/null
+++ b/pkgs/test/test/runner/github_reporter_test.dart
@@ -0,0 +1,377 @@
+// Copyright (c) 2022, 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')
+library;
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('reports when no tests are run', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ var test = await runTest(['test.dart'], reporter: 'github');
+ expect(test.stdout, emitsThrough(contains('0 tests passed')));
+ await test.shouldExit(79);
+ });
+
+ test('runs several successful tests and reports when each completes', () {
+ return _expectReport('''
+ test('success 1', () {});
+ test('success 2', () {});
+ test('success 3', () {});''', '''
+ ✅ success 1
+ ✅ success 2
+ ✅ success 3
+ 🎉 3 tests passed.''');
+ });
+
+ test('includes the platform name when multiple platforms are run', () {
+ return _expectReportLines('''
+ test('success 1', () {});''', [
+ '✅ [VM, Kernel] success 1',
+ '✅ [Chrome, Dart2Js] success 1',
+ '🎉 2 tests passed.',
+ ], args: [
+ '-p',
+ 'vm,chrome'
+ ]);
+ });
+
+ test('runs several failing tests and reports when each fails', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('failure 3', () => throw TestFailure('oh no'));''', '''
+ ::group::❌ failure 1 (failed)
+ oh no
+ test.dart 6:33 main.<fn>
+ ::endgroup::
+ ::group::❌ failure 2 (failed)
+ oh no
+ test.dart 7:33 main.<fn>
+ ::endgroup::
+ ::group::❌ failure 3 (failed)
+ oh no
+ test.dart 8:33 main.<fn>
+ ::endgroup::
+ ::error::0 tests passed, 3 failed.''');
+ });
+
+ test('includes the full stack trace with --verbose-trace', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw "oh no");
+}
+''').create();
+
+ var test =
+ await runTest(['--verbose-trace', 'test.dart'], reporter: 'github');
+ expect(test.stdout, emitsThrough(contains('dart:async')));
+ await test.shouldExit(1);
+ });
+
+ test('runs failing tests along with successful tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('success 2', () {});''', '''
+ ::group::❌ failure 1 (failed)
+ oh no
+ test.dart 6:33 main.<fn>
+ ::endgroup::
+ ✅ success 1
+ ::group::❌ failure 2 (failed)
+ oh no
+ test.dart 8:33 main.<fn>
+ ::endgroup::
+ ✅ success 2
+ ::error::2 tests passed, 2 failed.''');
+ });
+
+ test('always prints the full test name', () {
+ return _expectReport(
+ '''
+ test(
+ 'really gosh dang long test name. Even longer than that. No, yet '
+ 'longer. A little more... okay, that should do it.',
+ () {});''',
+ '✅ really gosh dang long test name. Even longer than that. No, yet longer. A little more... okay, that should do it.',
+ useContains: true,
+ );
+ });
+
+ test('gracefully handles multiple test failures in a row', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // errors have been thrown.
+ var completer = Completer();
+ test('failures', () {
+ Future.microtask(() => throw 'first error');
+ Future.microtask(() => throw 'second error');
+ Future.microtask(() => throw 'third error');
+ Future.microtask(completer.complete);
+ });
+ test('wait', () => completer.future);''', '''
+ ::group::❌ failures (failed)
+ first error
+ test.dart 10:34 main.<fn>.<fn>
+ second error
+ test.dart 11:34 main.<fn>.<fn>
+ third error
+ test.dart 12:34 main.<fn>.<fn>
+ ::endgroup::
+ ✅ wait
+ ::error::1 test passed, 1 failed.''');
+ });
+
+ test('displays failures occuring after a test completes', () {
+ return _expectReport(
+ '''
+ test('fail after completion', () {
+ Future(() {
+ Zone.current.handleUncaughtError('foo', StackTrace.current);
+ });
+ });
+
+ test('second test so that the first failure is reported', () {});''',
+ '''
+ ✅ fail after completion
+ ::group::❌ fail after completion (failed after test completion)
+ foo
+ test.dart 8:62 main.<fn>.<fn>
+ ::endgroup::
+ ::group::❌ fail after completion (failed after test completion)
+ This test failed after it had already completed.
+ Make sure to use a matching library which informs the test runner
+ of pending async work.
+ test.dart 8:62 main.<fn>.<fn>
+ ::endgroup::
+ ✅ second test so that the first failure is reported
+ ::error::1 test passed, 1 failed.''',
+ );
+ });
+
+ group('print:', () {
+ test('handles multiple prints', () {
+ return _expectReport(
+ '''
+ test('test', () {
+ print("one");
+ print("two");
+ print("three");
+ print("four");
+ });''',
+ '''
+ ::group::✅ test
+ one
+ two
+ three
+ four
+ ::endgroup::''',
+ useContains: true,
+ );
+ });
+
+ test('handles a print after the test completes', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var testDone = Completer();
+ var waitStarted = Completer();
+ test('test', () async {
+ waitStarted.future.then((_) {
+ Future(() => print("one"));
+ Future(() => print("two"));
+ Future(() => print("three"));
+ Future(() => print("four"));
+ Future(testDone.complete);
+ });
+ });
+
+ test('wait', () {
+ waitStarted.complete();
+ return testDone.future;
+ });''', '''
+ ✅ test
+ one
+ two
+ three
+ four
+ ✅ wait
+ 🎉 2 tests passed.''');
+ });
+ });
+
+ group('skip:', () {
+ test('displays skipped tests', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('skip 2', () {}, skip: true);
+ test('skip 3', () {}, skip: true);''', '''
+ ❎ skip 1 (skipped)
+ ❎ skip 2 (skipped)
+ ❎ skip 3 (skipped)
+ 🎉 0 tests passed, 3 skipped.''');
+ });
+
+ test('displays a skipped group', () {
+ return _expectReport('''
+ group('skip', () {
+ test('test 1', () {});
+ test('test 2', () {});
+ test('test 3', () {});
+ }, skip: true);''', '''
+ ❎ skip test 1 (skipped)
+ ❎ skip test 2 (skipped)
+ ❎ skip test 3 (skipped)
+ 🎉 0 tests passed, 3 skipped.''');
+ });
+
+ test('runs skipped tests along with successful tests', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('success 1', () {});
+ test('skip 2', () {}, skip: true);
+ test('success 2', () {});''', '''
+ ❎ skip 1 (skipped)
+ ✅ success 1
+ ❎ skip 2 (skipped)
+ ✅ success 2
+ 🎉 2 tests passed, 2 skipped.''');
+ });
+
+ test('runs skipped tests along with successful and failing tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('skip 1', () {}, skip: true);
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('skip 2', () {}, skip: true);
+ test('success 2', () {});''', '''
+ ::group::❌ failure 1 (failed)
+ oh no
+ test.dart 6:35 main.<fn>
+ ::endgroup::
+ ❎ skip 1 (skipped)
+ ✅ success 1
+ ::group::❌ failure 2 (failed)
+ oh no
+ test.dart 9:35 main.<fn>
+ ::endgroup::
+ ❎ skip 2 (skipped)
+ ✅ success 2
+ ::error::2 tests passed, 2 failed, 2 skipped.''');
+ });
+
+ test('displays the skip reason if available', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: 'some reason');
+ test('skip 2', () {}, skip: 'or another');''', '''
+ ::group::❎ skip 1 (skipped)
+ Skip: some reason
+ ::endgroup::
+ ::group::❎ skip 2 (skipped)
+ Skip: or another
+ ::endgroup::
+ 🎉 0 tests passed, 2 skipped.''');
+ });
+ });
+
+ test('loadSuite, setUpAll, and tearDownAll hidden if no content', () {
+ return _expectReport('''
+ group('one', () {
+ setUpAll(() {/* nothing to do here */});
+ tearDownAll(() {/* nothing to do here */});
+ test('test 1', () {});
+ });''', '''
+ ✅ one test 1
+ 🎉 1 test passed.''');
+ });
+
+ test('setUpAll and tearDownAll show when they have content', () {
+ return _expectReport('''
+ group('one', () {
+ setUpAll(() { print('one'); });
+ tearDownAll(() { print('two'); });
+ test('test 1', () {});
+ });''', '''
+ ::group::✅ one (setUpAll)
+ one
+ ::endgroup::
+ ✅ one test 1
+ ::group::✅ one (tearDownAll)
+ two
+ ::endgroup::
+ 🎉 1 test passed.''');
+ });
+}
+
+/// Expects exactly [expected] to appear in the test output.
+///
+/// If [useContains] is passed, then the output only must contain [expected].
+Future<void> _expectReport(
+ String tests,
+ String expected, {
+ List<String> args = const [],
+ bool useContains = false,
+}) async {
+ expected = expected.split('\n').map(_unindent).join('\n');
+
+ var actual = (await _reportLines(tests, args)).join('\n');
+
+ expect(actual, useContains ? contains(expected) : equals(expected));
+}
+
+/// Expects all of [expected] lines to appear in the test output, but additional
+/// output is allowed.
+Future<void> _expectReportLines(
+ String tests,
+ List<String> expected, {
+ List<String> args = const [],
+}) async {
+ expected = [for (var line in expected) _unindent(line)];
+ var actual = await _reportLines(tests, args);
+ expect(actual, containsAllInOrder(expected));
+}
+
+/// All the output lines from running [tests].
+Future<List<String>> _reportLines(String tests, List<String> args) async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+$tests
+ }
+ ''').create();
+
+ var test = await runTest([
+ 'test.dart',
+ ...args,
+ ], reporter: 'github');
+ await test.shouldExit();
+
+ var stdoutLines = await test.stdoutStream().toList();
+ return stdoutLines
+ .map((line) => line.trim())
+ .where((line) => line.isNotEmpty)
+ .toList();
+}
+
+/// Removes all leading space from [line].
+String _unindent(String line) => line.trimLeft();
diff --git a/pkgs/test/test/runner/json_file_reporter_test.dart b/pkgs/test/test/runner/json_file_reporter_test.dart
new file mode 100644
index 0000000..ce3bd42
--- /dev/null
+++ b/pkgs/test/test/runner/json_file_reporter_test.dart
@@ -0,0 +1,155 @@
+// Copyright (c) 2020, 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')
+library;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+import 'json_reporter_utils.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('runs successful tests with a stdout reporter and file reporter', () {
+ return _expectReports('''
+ test('success 1', () {});
+ test('success 2', () {});
+ test('success 3', () {});
+ ''', '''
+ +0: loading test.dart
+ +0: success 1
+ +1: success 2
+ +2: success 3
+ +3: All tests passed!''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 3),
+ testStartJson(3, 'success 1', line: 6, column: 7),
+ testDoneJson(3),
+ testStartJson(4, 'success 2', line: 7, column: 7),
+ testDoneJson(4),
+ testStartJson(5, 'success 3', line: 8, column: 7),
+ testDoneJson(5),
+ ]
+ ], doneJson());
+ });
+
+ test('runs failing tests with a stdout reporter and file reporter', () {
+ return _expectReports('''
+ test('failure 1', () => throw new TestFailure('oh no'));
+ test('failure 2', () => throw new TestFailure('oh no'));
+ test('failure 3', () => throw new TestFailure('oh no'));
+ ''', '''
+ +0: loading test.dart
+ +0: failure 1
+ +0 -1: failure 1 [E]
+ oh no
+ test.dart 6:31 main.<fn>
+
+ +0 -1: failure 2
+ +0 -2: failure 2 [E]
+ oh no
+ test.dart 7:31 main.<fn>
+
+ +0 -2: failure 3
+ +0 -3: failure 3 [E]
+ oh no
+ test.dart 8:31 main.<fn>
+
+ +0 -3: Some tests failed.''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 3),
+ testStartJson(3, 'failure 1', line: 6, column: 7),
+ errorJson(3, 'oh no', isFailure: true),
+ testDoneJson(3, result: 'failure'),
+ testStartJson(4, 'failure 2', line: 7, column: 7),
+ errorJson(4, 'oh no', isFailure: true),
+ testDoneJson(4, result: 'failure'),
+ testStartJson(5, 'failure 3', line: 8, column: 7),
+ errorJson(5, 'oh no', isFailure: true),
+ testDoneJson(5, result: 'failure'),
+ ]
+ ], doneJson(success: false));
+ });
+
+ group('reports an error if --file-reporter', () {
+ test('is not in the form <reporter>:<filepath>', () async {
+ var test = await runTest(['--file-reporter=json']);
+ expect(test.stderr,
+ emits(contains('option must be in the form <reporter>:<filepath>')));
+ await test.shouldExit(exit_codes.usage);
+ });
+
+ test('targets a non-existent reporter', () async {
+ var test = await runTest(['--file-reporter=nope:output.txt']);
+ expect(
+ test.stderr, emits(contains('"nope" is not a supported reporter')));
+ await test.shouldExit(exit_codes.usage);
+ });
+ });
+}
+
+Future<void> _expectReports(
+ String tests,
+ String stdoutExpected,
+ List<List<Object /*Map|Matcher*/ >> jsonFileExpected,
+ Map<Object, Object> jsonFileDone,
+ {List<String> args = const []}) async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+$tests
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart', '--chain-stack-traces', ...args],
+ // Write to a file within a dir that doesn't yet exist to verify that the
+ // file is created recursively.
+ fileReporter: 'json:reports/tests.json');
+ await test.shouldExit();
+
+ // ---- stdout reporter verification ----
+ var stdoutLines = await test.stdoutStream().toList();
+
+ // Remove excess trailing whitespace and trim off timestamps.
+ var actual = stdoutLines.map((line) {
+ if (line.startsWith(' ') || line.isEmpty) return line.trimRight();
+ return line.trim().replaceFirst(RegExp('^[0-9]{2}:[0-9]{2} '), '');
+ }).join('\n');
+
+ // Un-indent the expected string.
+ var indentation = stdoutExpected.indexOf(RegExp('[^ ]'));
+ stdoutExpected = stdoutExpected.split('\n').map((line) {
+ if (line.isEmpty) return line;
+ return line.substring(indentation);
+ }).join('\n');
+
+ expect(actual, equals(stdoutExpected));
+
+ // ---- file reporter verification ----
+ var fileOutputLines =
+ File(p.join(d.sandbox, 'reports', 'tests.json')).readAsLinesSync();
+ await expectJsonReport(
+ fileOutputLines, test.pid, jsonFileExpected, jsonFileDone);
+}
diff --git a/pkgs/test/test/runner/json_reporter_test.dart b/pkgs/test/test/runner/json_reporter_test.dart
new file mode 100644
index 0000000..a771438
--- /dev/null
+++ b/pkgs/test/test/runner/json_reporter_test.dart
@@ -0,0 +1,655 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:async';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+import 'json_reporter_utils.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('runs several successful tests and reports when each completes', () {
+ return _expectReport('''
+ test('success 1', () {});
+ test('success 2', () {});
+ test('success 3', () {});
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 3),
+ testStartJson(3, 'success 1', line: 6, column: 7),
+ testDoneJson(3),
+ testStartJson(4, 'success 2', line: 7, column: 7),
+ testDoneJson(4),
+ testStartJson(5, 'success 3', line: 8, column: 7),
+ testDoneJson(5),
+ ]
+ ], doneJson());
+ });
+
+ test('runs several failing tests and reports when each fails', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('failure 3', () => throw TestFailure('oh no'));
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 3),
+ testStartJson(3, 'failure 1', line: 6, column: 7),
+ errorJson(3, 'oh no', isFailure: true),
+ testDoneJson(3, result: 'failure'),
+ testStartJson(4, 'failure 2', line: 7, column: 7),
+ errorJson(4, 'oh no', isFailure: true),
+ testDoneJson(4, result: 'failure'),
+ testStartJson(5, 'failure 3', line: 8, column: 7),
+ errorJson(5, 'oh no', isFailure: true),
+ testDoneJson(5, result: 'failure'),
+ ]
+ ], doneJson(success: false));
+ });
+
+ test('includes the full stack trace with --verbose-trace', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () => throw "oh no");
+ }
+ ''').create();
+
+ var test =
+ await runTest(['--verbose-trace', 'test.dart'], reporter: 'json');
+ expect(test.stdout, emitsThrough(contains('dart:async')));
+ await test.shouldExit(1);
+ });
+
+ test('runs failing tests along with successful tests', () {
+ return _expectReport('''
+ test('failure 1', () => throw TestFailure('oh no'));
+ test('success 1', () {});
+ test('failure 2', () => throw TestFailure('oh no'));
+ test('success 2', () {});
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 4),
+ testStartJson(3, 'failure 1', line: 6, column: 7),
+ errorJson(3, 'oh no', isFailure: true),
+ testDoneJson(3, result: 'failure'),
+ testStartJson(4, 'success 1', line: 7, column: 7),
+ testDoneJson(4),
+ testStartJson(5, 'failure 2', line: 8, column: 7),
+ errorJson(5, 'oh no', isFailure: true),
+ testDoneJson(5, result: 'failure'),
+ testStartJson(6, 'success 2', line: 9, column: 7),
+ testDoneJson(6),
+ ]
+ ], doneJson(success: false));
+ });
+
+ test('gracefully handles multiple test failures in a row', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // errors have been thrown.
+ var completer = Completer();
+ test('failures', () {
+ Future.microtask(() => throw 'first error');
+ Future.microtask(() => throw 'second error');
+ Future.microtask(() => throw 'third error');
+ Future.microtask(completer.complete);
+ });
+ test('wait', () => completer.future);
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'failures', line: 9, column: 7),
+ errorJson(3, 'first error'),
+ errorJson(3, 'second error'),
+ errorJson(3, 'third error'),
+ testDoneJson(3, result: 'error'),
+ testStartJson(4, 'wait', line: 15, column: 7),
+ testDoneJson(4),
+ ]
+ ], doneJson(success: false));
+ });
+
+ test('gracefully handles a test failing after completion', () {
+ return _expectReport('''
+ // These completers ensure that the first test won't fail until the second
+ // one is running, and that the test isolate isn't killed until all errors
+ // have been thrown.
+ var waitStarted = Completer();
+ var testDone = Completer();
+ test('failure', () {
+ waitStarted.future.then((_) {
+ Future.microtask(testDone.complete);
+ throw 'oh no';
+ });
+ });
+ test('wait', () {
+ waitStarted.complete();
+ return testDone.future;
+ });
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'failure', line: 11, column: 7),
+ testDoneJson(3),
+ testStartJson(4, 'wait', line: 17, column: 7),
+ errorJson(3, 'oh no'),
+ errorJson(
+ 3,
+ 'This test failed after it had already completed.\n'
+ 'Make sure to use a matching library which informs the '
+ 'test runner\nof pending async work.'),
+ testDoneJson(4),
+ ]
+ ], doneJson(success: false));
+ });
+
+ test('reports each test in its proper groups', () {
+ return _expectReport('''
+ group('group 1', () {
+ group('.2', () {
+ group('.3', () {
+ test('success', () {});
+ });
+ });
+
+ test('success1', () {});
+ test('success2', () {});
+ });
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 3),
+ groupJson(3,
+ name: 'group 1', parentID: 2, testCount: 3, line: 6, column: 7),
+ groupJson(4, name: 'group 1 .2', parentID: 3, line: 7, column: 9),
+ groupJson(5, name: 'group 1 .2 .3', parentID: 4, line: 8, column: 11),
+ testStartJson(6, 'group 1 .2 .3 success',
+ groupIDs: [2, 3, 4, 5], line: 9, column: 13),
+ testDoneJson(6),
+ testStartJson(7, 'group 1 success1',
+ groupIDs: [2, 3], line: 13, column: 9),
+ testDoneJson(7),
+ testStartJson(8, 'group 1 success2',
+ groupIDs: [2, 3], line: 14, column: 9),
+ testDoneJson(8),
+ ]
+ ], doneJson());
+ });
+
+ group('print:', () {
+ test('handles multiple prints', () {
+ return _expectReport('''
+ test('test', () {
+ print("one");
+ print("two");
+ print("three");
+ print("four");
+ });
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2),
+ testStartJson(3, 'test', line: 6, column: 9),
+ printJson(3, 'one'),
+ printJson(3, 'two'),
+ printJson(3, 'three'),
+ printJson(3, 'four'),
+ testDoneJson(3),
+ ]
+ ], doneJson());
+ });
+
+ test('handles a print after the test completes', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var testDone = Completer();
+ var waitStarted = Completer();
+ test('test', () async {
+ waitStarted.future.then((_) {
+ Future(() => print("one"));
+ Future(() => print("two"));
+ Future(() => print("three"));
+ Future(() => print("four"));
+ Future(testDone.complete);
+ });
+ });
+
+ test('wait', () {
+ waitStarted.complete();
+ return testDone.future;
+ });
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'test', line: 10, column: 9),
+ testDoneJson(3),
+ testStartJson(4, 'wait', line: 20, column: 9),
+ printJson(3, 'one'),
+ printJson(3, 'two'),
+ printJson(3, 'three'),
+ printJson(3, 'four'),
+ testDoneJson(4),
+ ]
+ ], doneJson());
+ });
+
+ test('interleaves prints and errors', () {
+ return _expectReport('''
+ // This completer ensures that the test isolate isn't killed until all
+ // prints have happened.
+ var completer = Completer();
+ test('test', () {
+ scheduleMicrotask(() {
+ print("three");
+ print("four");
+ throw "second error";
+ });
+
+ scheduleMicrotask(() {
+ print("five");
+ print("six");
+ completer.complete();
+ });
+
+ print("one");
+ print("two");
+ throw "first error";
+ });
+
+ test('wait', () => completer.future);
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'test', line: 9, column: 9),
+ printJson(3, 'one'),
+ printJson(3, 'two'),
+ errorJson(3, 'first error'),
+ printJson(3, 'three'),
+ printJson(3, 'four'),
+ errorJson(3, 'second error'),
+ printJson(3, 'five'),
+ printJson(3, 'six'),
+ testDoneJson(3, result: 'error'),
+ testStartJson(4, 'wait', line: 27, column: 9),
+ testDoneJson(4),
+ ]
+ ], doneJson(success: false));
+ });
+ });
+
+ group('skip:', () {
+ test('reports skipped tests', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: true);
+ test('skip 2', () {}, skip: true);
+ test('skip 3', () {}, skip: true);
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 3),
+ testStartJson(3, 'skip 1', skip: true, line: 6, column: 9),
+ testDoneJson(3, skipped: true),
+ testStartJson(4, 'skip 2', skip: true, line: 7, column: 9),
+ testDoneJson(4, skipped: true),
+ testStartJson(5, 'skip 3', skip: true, line: 8, column: 9),
+ testDoneJson(5, skipped: true),
+ ]
+ ], doneJson());
+ });
+
+ test('reports skipped groups', () {
+ return _expectReport('''
+ group('skip', () {
+ test('success 1', () {});
+ test('success 2', () {});
+ test('success 3', () {});
+ }, skip: true);
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 3),
+ groupJson(3,
+ name: 'skip',
+ parentID: 2,
+ skip: true,
+ testCount: 3,
+ line: 6,
+ column: 9),
+ testStartJson(4, 'skip success 1',
+ groupIDs: [2, 3], skip: true, line: 7, column: 11),
+ testDoneJson(4, skipped: true),
+ testStartJson(5, 'skip success 2',
+ groupIDs: [2, 3], skip: true, line: 8, column: 11),
+ testDoneJson(5, skipped: true),
+ testStartJson(6, 'skip success 3',
+ groupIDs: [2, 3], skip: true, line: 9, column: 11),
+ testDoneJson(6, skipped: true),
+ ]
+ ], doneJson());
+ });
+
+ test('reports the skip reason if available', () {
+ return _expectReport('''
+ test('skip 1', () {}, skip: 'some reason');
+ test('skip 2', () {}, skip: 'or another');
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'skip 1', skip: 'some reason', line: 6, column: 9),
+ printJson(3, 'Skip: some reason', type: 'skip'),
+ testDoneJson(3, skipped: true),
+ testStartJson(4, 'skip 2', skip: 'or another', line: 7, column: 9),
+ printJson(4, 'Skip: or another', type: 'skip'),
+ testDoneJson(4, skipped: true),
+ ]
+ ], doneJson());
+ });
+
+ test('runs skipped tests with --run-skipped', () {
+ return _expectReport(
+ '''
+ test('skip 1', () {}, skip: 'some reason');
+ test('skip 2', () {}, skip: 'or another');
+ ''',
+ [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'skip 1', line: 6, column: 9),
+ testDoneJson(3),
+ testStartJson(4, 'skip 2', line: 7, column: 9),
+ testDoneJson(4),
+ ]
+ ],
+ doneJson(),
+ args: ['--run-skipped']);
+ });
+ });
+
+ group('reports line and column numbers for', () {
+ test('the first call to setUpAll()', () {
+ return _expectReport('''
+ setUpAll(() {});
+ setUpAll(() {});
+ setUpAll(() {});
+ test('success', () {});
+ ''', [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 1),
+ testStartJson(3, '(setUpAll)', line: 6, column: 9),
+ testDoneJson(3, hidden: true),
+ testStartJson(4, 'success', line: 9, column: 9),
+ testDoneJson(4),
+ testStartJson(5, '(tearDownAll)'),
+ testDoneJson(5, hidden: true),
+ ]
+ ], doneJson());
+ });
+
+ test('the first call to tearDownAll()', () {
+ return _expectReport('''
+ tearDownAll(() {});
+ tearDownAll(() {});
+ tearDownAll(() {});
+ test('success', () {});
+ ''', [
+ [
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ suiteJson(0),
+ groupJson(2, testCount: 1),
+ testStartJson(3, 'success', line: 9, column: 9),
+ testDoneJson(3),
+ testStartJson(4, '(tearDownAll)', line: 6, column: 9),
+ testDoneJson(4, hidden: true),
+ ]
+ ], doneJson());
+ });
+
+ test('a test compiled to JS', () {
+ return _expectReport(
+ '''
+ test('success', () {});
+ ''',
+ [
+ [
+ suiteJson(0, platform: 'chrome'),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ printJson(
+ 1,
+ isA<String>().having((s) => s.split('\n'), 'lines',
+ contains(startsWith('Compiled')))),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 1),
+ testStartJson(3, 'success', line: 6, column: 9),
+ testDoneJson(3),
+ ]
+ ],
+ doneJson(),
+ args: ['-p', 'chrome']);
+ }, tags: ['chrome'], skip: 'https://github.com/dart-lang/test/issues/872');
+
+ test('the root suite from a relative path', () {
+ return _expectReport(
+ '''
+ customTest('success 1', () {});
+ test('success 2', () {});
+ ''',
+ [
+ [
+ suiteJson(0),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'success 1',
+ line: 3,
+ column: 60,
+ url: p.toUri(p.join(d.sandbox, 'common.dart')).toString(),
+ rootColumn: 7,
+ rootLine: 7,
+ rootUrl: p.toUri(p.join(d.sandbox, 'test.dart')).toString()),
+ testDoneJson(3),
+ testStartJson(4, 'success 2', line: 8, column: 7),
+ testDoneJson(4),
+ ]
+ ],
+ doneJson(),
+ externalLibraries: {
+ 'common.dart': '''
+import 'package:test/test.dart';
+
+void customTest(String name, dynamic Function() testFn) => test(name, testFn);
+''',
+ });
+ });
+
+ test('the root suite from an absolute path', () {
+ final path = p.prettyUri(p.join(d.sandbox, 'test.dart'));
+ return _expectReport(
+ '''
+ customTest('success 1', () {});
+ test('success 2', () {});
+ ''',
+ useRelativePath: false,
+ [
+ [
+ suiteJson(0, path: equalsIgnoringCase(path)),
+ testStartJson(
+ 1, allOf(startsWith('loading '), endsWith('test.dart')),
+ groupIDs: []),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 2),
+ testStartJson(3, 'success 1',
+ line: 3,
+ column: 60,
+ url: p.toUri(p.join(d.sandbox, 'common.dart')).toString(),
+ rootColumn: 7,
+ rootLine: 7,
+ rootUrl: p.toUri(p.join(d.sandbox, 'test.dart')).toString()),
+ testDoneJson(3),
+ testStartJson(4, 'success 2', line: 8, column: 7),
+ testDoneJson(4),
+ ]
+ ],
+ doneJson(),
+ externalLibraries: {
+ 'common.dart': '''
+import 'package:test/test.dart';
+
+void customTest(String name, dynamic Function() testFn) => test(name, testFn);
+''',
+ });
+ });
+ });
+
+ test(
+ "doesn't report line and column information for a test compiled to JS "
+ 'with --js-trace', () {
+ return _expectReport(
+ '''
+ test('success', () {});
+ ''',
+ [
+ [
+ suiteJson(0, platform: 'chrome'),
+ testStartJson(1, 'loading test.dart', groupIDs: []),
+ printJson(
+ 1,
+ isA<String>().having((s) => s.split('\n'), 'lines',
+ contains(startsWith('Compiled')))),
+ testDoneJson(1, hidden: true),
+ ],
+ [
+ groupJson(2, testCount: 1),
+ testStartJson(3, 'success'),
+ testDoneJson(3),
+ ],
+ ],
+ doneJson(),
+ args: ['-p', 'chrome', '--js-trace']);
+ }, tags: ['chrome']);
+}
+
+/// Asserts that the tests defined by [tests] produce the JSON events in
+/// [expected].
+///
+/// If [externalLibraries] are provided it should be a map of relative file
+/// paths to contents. All libraries will be added as imports to the test, and
+/// files will be created for them.
+Future<void> _expectReport(String tests,
+ List<List<Object /*Map|Matcher*/ >> expected, Map<Object, Object> done,
+ {List<String> args = const [],
+ bool useRelativePath = true,
+ Map<String, String> externalLibraries = const {}}) async {
+ var testContent = StringBuffer('''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+''');
+ for (var entry in externalLibraries.entries) {
+ testContent.writeln("import '${entry.key}';");
+ await d.file(entry.key, entry.value).create();
+ }
+ testContent
+ ..writeln('void main() {')
+ ..writeln(tests)
+ ..writeln('}');
+
+ await d.file('test.dart', testContent.toString()).create();
+ var testPath = useRelativePath ? 'test.dart' : p.join(d.sandbox, 'test.dart');
+
+ var test = await runTest([testPath, '--chain-stack-traces', ...args],
+ reporter: 'json');
+ await test.shouldExit();
+
+ var stdoutLines = await test.stdoutStream().toList();
+ return expectJsonReport(stdoutLines, test.pid, expected, done);
+}
diff --git a/pkgs/test/test/runner/json_reporter_utils.dart b/pkgs/test/test/runner/json_reporter_utils.dart
new file mode 100644
index 0000000..bbdbb78
--- /dev/null
+++ b/pkgs/test/test/runner/json_reporter_utils.dart
@@ -0,0 +1,209 @@
+// Copyright (c) 2020, 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:convert';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_core/src/runner/version.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+/// Asserts that the outputs from running tests with a JSON reporter match the
+/// given expectations.
+///
+/// Verifies that [outputLines] matches each set of matchers in [expected],
+/// includes the [testPid] from the test process, and ends with [done].
+Future<void> expectJsonReport(
+ List<String> outputLines,
+ int testPid,
+ List<List<Object /*Map|Matcher*/ >> expected,
+ Map<Object, Object> done) async {
+ // Ensure the output is of the same length, including start, done and all
+ // suites messages.
+ expect(outputLines,
+ hasLength(expected.fold<int>(3, (int a, m) => a + m.length)));
+
+ final decoded = [
+ for (final line in outputLines)
+ (jsonDecode(line) as Map)
+ ..remove('time')
+ ..remove('stackTrace')
+ ];
+
+ // Should contain all suites message.
+ expect(decoded, containsAll([_allSuitesJson()]));
+
+ // A single start event is emitted first.
+ final start = {
+ 'type': 'start',
+ 'protocolVersion': '0.1.1',
+ 'runnerVersion': testVersion,
+ 'pid': testPid,
+ };
+ expect(decoded.first, equals(start));
+
+ // A single done event is emitted last.
+ expect(decoded.last, equals(done));
+
+ for (var value in expected) {
+ expect(decoded, containsAllInOrder(value));
+ }
+}
+
+/// Returns the event emitted by the JSON reporter providing information about
+/// all suites.
+///
+/// The [count] defaults to 1.
+Map<String, Object> _allSuitesJson({int count = 1}) =>
+ {'type': 'allSuites', 'count': count};
+
+/// Returns the event emitted by the JSON reporter indicating that a suite has
+/// begun running.
+///
+/// The [platform] defaults to `'vm'`.
+/// The [path] defaults to `equals('test.dart')`.
+Map<String, Object> suiteJson(int id,
+ {String platform = 'vm', Matcher? path}) =>
+ {
+ 'type': 'suite',
+ 'suite': {
+ 'id': id,
+ 'platform': platform,
+ 'path': path ?? 'test.dart',
+ }
+ };
+
+/// Returns the event emitted by the JSON reporter indicating that a group has
+/// begun running.
+///
+/// If [skip] is `true`, the group is expected to be marked as skipped without a
+/// reason. If it's a [String], the group is expected to be marked as skipped
+/// with that reason.
+///
+/// The [testCount] parameter indicates the number of tests in the group. It
+/// defaults to 1.
+Map<String, Object> groupJson(int id,
+ {String? name,
+ int? suiteID,
+ int? parentID,
+ Object? skip,
+ int? testCount,
+ int? line,
+ int? column}) {
+ if ((line == null) != (column == null)) {
+ throw ArgumentError(
+ 'line and column must either both be null or both be passed');
+ }
+
+ return {
+ 'type': 'group',
+ 'group': {
+ 'id': id,
+ 'name': name ?? '',
+ 'suiteID': suiteID ?? 0,
+ 'parentID': parentID,
+ 'metadata': _metadataJson(skip: skip),
+ 'testCount': testCount ?? 1,
+ 'line': line,
+ 'column': column,
+ 'url': line == null
+ ? null
+ : p.toUri(p.join(d.sandbox, 'test.dart')).toString()
+ }
+ };
+}
+
+/// Returns the event emitted by the JSON reporter indicating that a test has
+/// begun running.
+///
+/// If [parentIDs] is passed, it's the IDs of groups containing this test. If
+/// [skip] is `true`, the test is expected to be marked as skipped without a
+/// reason. If it's a [String], the test is expected to be marked as skipped
+/// with that reason.
+Map<String, Object> testStartJson(int id, Object /*String|Matcher*/ name,
+ {int? suiteID,
+ Iterable<int>? groupIDs,
+ int? line,
+ int? column,
+ String? url,
+ Object? skip,
+ int? rootLine,
+ int? rootColumn,
+ String? rootUrl}) {
+ if ((line == null) != (column == null)) {
+ throw ArgumentError(
+ 'line and column must either both be null or both be passed');
+ }
+
+ url ??=
+ line == null ? null : p.toUri(p.join(d.sandbox, 'test.dart')).toString();
+ return {
+ 'type': 'testStart',
+ 'test': {
+ 'id': id,
+ 'name': name,
+ 'suiteID': suiteID ?? 0,
+ 'groupIDs': groupIDs ?? [2],
+ 'metadata': _metadataJson(skip: skip),
+ 'line': line,
+ 'column': column,
+ 'url': url,
+ if (rootLine != null) 'root_line': rootLine,
+ if (rootColumn != null) 'root_column': rootColumn,
+ if (rootUrl != null) 'root_url': rootUrl,
+ }
+ };
+}
+
+/// Returns the event emitted by the JSON reporter indicating that a test
+/// printed [message].
+Matcher printJson(int id, dynamic /*String|Matcher*/ message,
+ {String type = 'print'}) =>
+ allOf(
+ hasLength(4),
+ containsPair('type', 'print'),
+ containsPair('testID', id),
+ containsPair('message', message),
+ containsPair('messageType', type),
+ );
+
+/// Returns the event emitted by the JSON reporter indicating that a test
+/// emitted [error].
+///
+/// The [isFailure] parameter indicates whether the error was a [TestFailure] or
+/// not.
+Map<String, Object> errorJson(int id, String error, {bool isFailure = false}) =>
+ {'type': 'error', 'testID': id, 'error': error, 'isFailure': isFailure};
+
+/// Returns the event emitted by the JSON reporter indicating that a test
+/// finished.
+///
+/// The [result] parameter indicates the result of the test. It defaults to
+/// `"success"`.
+///
+/// The [hidden] parameter indicates whether the test should not be displayed
+/// after finishing. The [skipped] parameter indicates whether the test was
+/// skipped.
+Map<String, Object> testDoneJson(int id,
+ {String result = 'success',
+ bool hidden = false,
+ bool skipped = false}) =>
+ {
+ 'type': 'testDone',
+ 'testID': id,
+ 'result': result,
+ 'hidden': hidden,
+ 'skipped': skipped
+ };
+
+/// Returns the event emitted by the JSON reporter indicating that the entire
+/// run finished.
+Map<String, Object> doneJson({bool success = true}) =>
+ {'type': 'done', 'success': success};
+
+/// Returns the serialized metadata corresponding to [skip].
+Map<String, Object?> _metadataJson({Object? skip}) => {
+ 'skip': skip == true || skip is String,
+ 'skipReason': skip is String ? skip : null
+ };
diff --git a/pkgs/test/test/runner/line_and_col_test.dart b/pkgs/test/test/runner/line_and_col_test.dart
new file mode 100644
index 0000000..c513cef
--- /dev/null
+++ b/pkgs/test/test/runner/line_and_col_test.dart
@@ -0,0 +1,386 @@
+// Copyright (c) 2021, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('with test.dart?line=<line> query', () {
+ test('selects test with the matching line', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ test("b", () => throw TestFailure("oh no"));
+ test("c", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=6']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('selects multiple tests on the same line', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {}); test("b", () {});
+ test("c", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=4']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('selects groups with a matching line', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("a", () {
+ test("b", () {});
+ });
+ group("b", () {
+ test("b", () => throw TestFailure("oh no"));
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=4']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('No matching tests', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=1']);
+
+ expect(
+ test.stderr,
+ emitsThrough(contains('No tests were found.')),
+ );
+
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+
+ test('allows the line anywhere in the stack trace', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void runTest(String name) {
+ test(name, () {});
+ }
+
+ void main() {
+ runTest("a");
+ test("b", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=8']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+ });
+
+ group('with test.dart?col=<col> query', () {
+ test('selects single test with the matching column', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ test("b", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?col=11']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('selects multiple tests starting on the same column', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ test("b", () {});
+ test("c", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?col=11']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('selects groups with a matching column', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("a", () {
+ test("b", () {});
+ });
+ group("b", () {
+ test("b", () => throw TestFailure("oh no"));
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?col=11']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('No matching tests', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?col=1']);
+
+ expect(
+ test.stderr,
+ emitsThrough(contains('No tests were found.')),
+ );
+
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+
+ test('allows the col anywhere in the stack trace', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void runTest(String name) {
+ test(name, () {});
+ }
+
+ void main() {
+ runTest("a");
+ test("b", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?col=13']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+ });
+
+ group('with test.dart?line=<line>&col=<col> query', () {
+ test('selects test with the matching line and col in the same frame',
+ () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ void runTests() {
+ test("a", () {});test("b", () => throw TestFailure("oh no"));
+ }
+ runTests();
+ test("c", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=5&col=11']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('selects group with the matching line and col', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("a", () {
+ test("b", () {});
+ test("c", () {});
+ });
+ group("d", () {
+ test("e", () => throw TestFailure("oh no"));
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=4&col=11']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('no matching tests - col doesnt match', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=4&col=1']);
+
+ expect(
+ test.stderr,
+ emitsThrough(contains('No tests were found.')),
+ );
+
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+
+ test('no matching tests - line doesnt match', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=1&col=11']);
+
+ expect(
+ test.stderr,
+ emitsThrough(contains('No tests were found.')),
+ );
+
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+
+ test('supports browser tests', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ test("b", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=4&col=11', '-p', 'chrome']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('supports node tests', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("a", () {});
+ test("b", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=4&col=11', '-p', 'node']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ }, retry: 3);
+ });
+
+ test('bundles runs by suite, deduplicates tests that match multiple times',
+ () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test('a', () {});
+ test('b', () => throw TestFailure('oh no'));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?line=4', 'test.dart?full-name=a']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/load_suite_test.dart b/pkgs/test/test/runner/load_suite_test.dart
new file mode 100644
index 0000000..ab5fc79
--- /dev/null
+++ b/pkgs/test/test/runner/load_suite_test.dart
@@ -0,0 +1,178 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/group.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_api/src/backend/test.dart';
+import 'package:test_core/src/runner/load_exception.dart';
+import 'package:test_core/src/runner/load_suite.dart';
+import 'package:test_core/src/runner/runner_suite.dart';
+import 'package:test_core/src/runner/suite.dart';
+
+import '../utils.dart';
+
+void main() {
+ late RunnerSuite innerSuite;
+ setUp(() {
+ innerSuite = runnerSuite(Group.root([]));
+ });
+
+ test('running a load test causes LoadSuite.suite to emit a suite', () async {
+ var suite = LoadSuite('name', SuiteConfiguration.empty, suitePlatform,
+ () => Future.value(innerSuite));
+ expect(suite.group.entries, hasLength(1));
+
+ expect(suite.suite, completion(equals(innerSuite)));
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ await liveTest.run();
+ expectTestPassed(liveTest);
+ });
+
+ test("running a load suite's body may be synchronous", () async {
+ var suite = LoadSuite(
+ 'name', SuiteConfiguration.empty, suitePlatform, () => innerSuite);
+ expect(suite.group.entries, hasLength(1));
+
+ expect(suite.suite, completion(equals(innerSuite)));
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ await liveTest.run();
+ expectTestPassed(liveTest);
+ });
+
+ test("a load test doesn't complete until the body returns", () async {
+ var completer = Completer<RunnerSuite>();
+ var suite = LoadSuite('name', SuiteConfiguration.empty, suitePlatform,
+ () => completer.future);
+ expect(suite.group.entries, hasLength(1));
+
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ expect(liveTest.run(), completes);
+ await Future<void>.delayed(Duration.zero);
+ expect(liveTest.state.status, equals(Status.running));
+
+ completer.complete(innerSuite);
+ await Future<void>.delayed(Duration.zero);
+ expectTestPassed(liveTest);
+ });
+
+ test('a load test forwards errors and completes LoadSuite.suite to null',
+ () async {
+ var suite = LoadSuite('name', SuiteConfiguration.empty, suitePlatform, () {
+ return fail('error');
+ });
+ expect(suite.group.entries, hasLength(1));
+
+ expect(suite.suite, completion(isNull));
+
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ await liveTest.run();
+ expectTestFailed(liveTest, 'error');
+ });
+
+ test("a load test completes early if it's closed", () async {
+ var suite = LoadSuite('name', SuiteConfiguration.empty, suitePlatform,
+ () => Completer<RunnerSuite>().future);
+ expect(suite.group.entries, hasLength(1));
+
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ expect(liveTest.run(), completes);
+ await Future<void>.delayed(Duration.zero);
+ expect(liveTest.state.status, equals(Status.running));
+
+ expect(liveTest.close(), completes);
+ });
+
+ test('forLoadException() creates a suite that completes to a LoadException',
+ () async {
+ var exception = LoadException('path', 'error');
+ var suite = LoadSuite.forLoadException(exception, SuiteConfiguration.empty);
+ expect(suite.group.entries, hasLength(1));
+
+ expect(suite.suite, completion(isNull));
+
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ await liveTest.run();
+ expect(liveTest.state.status, equals(Status.complete));
+ expect(liveTest.state.result, equals(Result.error));
+ expect(liveTest.errors, hasLength(1));
+ expect(liveTest.errors.first.error, equals(exception));
+ });
+
+ test('forSuite() creates a load suite that completes to a test suite',
+ () async {
+ var suite = LoadSuite.forSuite(innerSuite);
+ expect(suite.group.entries, hasLength(1));
+
+ expect(suite.suite, completion(equals(innerSuite)));
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ await liveTest.run();
+ expectTestPassed(liveTest);
+ });
+
+ group('changeSuite()', () {
+ test('returns a new load suite with the same properties', () {
+ var suite = LoadSuite(
+ 'name', SuiteConfiguration.empty, suitePlatform, () => innerSuite);
+ expect(suite.group.entries, hasLength(1));
+
+ var newSuite = suite.changeSuite((suite) => suite);
+ expect(newSuite.platform.runtime, equals(Runtime.vm));
+ expect(newSuite.group.entries.single.name,
+ equals(suite.group.entries.single.name));
+ });
+
+ test('changes the inner suite', () async {
+ var suite = LoadSuite(
+ 'name', SuiteConfiguration.empty, suitePlatform, () => innerSuite);
+ expect(suite.group.entries, hasLength(1));
+
+ var newInnerSuite = runnerSuite(Group.root([]));
+ var newSuite = suite.changeSuite((suite) => newInnerSuite);
+ expect(newSuite.suite, completion(equals(newInnerSuite)));
+
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ await liveTest.run();
+ expectTestPassed(liveTest);
+ });
+
+ test("doesn't run change() if the suite is null", () async {
+ var suite = LoadSuite(
+ 'name', SuiteConfiguration.empty, suitePlatform, () => null);
+ expect(suite.group.entries, hasLength(1));
+
+ var newSuite = suite.changeSuite(expectAsync1((_) {
+ return null;
+ }, count: 0));
+ expect(newSuite.suite, completion(isNull));
+
+ var liveTest = (suite.group.entries.single as Test).load(suite);
+ await liveTest.run();
+ expectTestPassed(liveTest);
+ });
+ });
+
+ group('getSuite()', () {
+ test('runs the test and returns the suite', () {
+ var suite = LoadSuite.forSuite(innerSuite);
+ expect(suite.group.entries, hasLength(1));
+
+ expect(suite.getSuite(), completion(equals(innerSuite)));
+ });
+
+ test('forwards errors to the future', () {
+ var suite = LoadSuite(
+ 'name', SuiteConfiguration.empty, suitePlatform, () => throw 'error');
+ expect(suite.group.entries, hasLength(1));
+
+ expect(suite.getSuite(), throwsA('error'));
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/loader_test.dart b/pkgs/test/test/runner/loader_test.dart
new file mode 100644
index 0000000..8d1d4ad
--- /dev/null
+++ b/pkgs/test/test/runner/loader_test.dart
@@ -0,0 +1,293 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:async';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/compiler.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_api/src/backend/test.dart';
+import 'package:test_core/src/runner/compiler_selection.dart';
+import 'package:test_core/src/runner/load_suite.dart';
+import 'package:test_core/src/runner/loader.dart';
+import 'package:test_core/src/runner/runner_suite.dart';
+import 'package:test_core/src/runner/runner_test.dart';
+import 'package:test_core/src/runner/suite.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../utils.dart';
+
+late Loader _loader;
+
+final _tests = '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+ test("failure", () => throw TestFailure('oh no'));
+ test("error", () => throw 'oh no');
+}
+''';
+
+void main() {
+ setUp(() async {
+ _loader = Loader();
+ });
+
+ tearDown(() => _loader.close());
+
+ group('.loadFile()', () {
+ late RunnerSuite suite;
+ group('with empty configuration', () {
+ setUp(() async {
+ await d.file('a_test.dart', _tests).create();
+ var suites = await _loader
+ .loadFile(
+ p.join(d.sandbox, 'a_test.dart'), SuiteConfiguration.empty)
+ .toList();
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ suite = (await loadSuite.getSuite())!;
+ });
+
+ test('returns a suite with the file path and platform', () {
+ expect(suite.path, equals(p.join(d.sandbox, 'a_test.dart')));
+ expect(suite.platform.runtime, equals(Runtime.vm));
+ expect(suite.platform.compiler, equals(Runtime.vm.defaultCompiler));
+ });
+
+ test('returns entries with the correct names and platforms', () {
+ expect(suite.group.entries, hasLength(3));
+ expect(suite.group.entries[0].name, equals('success'));
+ expect(suite.group.entries[1].name, equals('failure'));
+ expect(suite.group.entries[2].name, equals('error'));
+ });
+
+ test('can load and run a successful test', () {
+ var liveTest = (suite.group.entries[0] as RunnerTest).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.success)
+ ]);
+ expectErrors(liveTest, []);
+
+ return liveTest.run().whenComplete(() => liveTest.close());
+ });
+
+ test('can load and run a failing test', () {
+ var liveTest = (suite.group.entries[1] as RunnerTest).load(suite);
+ expectSingleFailure(liveTest);
+ return liveTest.run().whenComplete(() => liveTest.close());
+ });
+ });
+
+ group('with compiler selection', () {
+ Future<List<LoadSuite>> loadSuitesWithConfig(
+ SuiteConfiguration suiteConfiguration) async {
+ await d.file('a_test.dart', _tests).create();
+ return _loader
+ .loadFile(p.join(d.sandbox, 'a_test.dart'), suiteConfiguration)
+ .toList();
+ }
+
+ test('with a single compiler selection, uses the selected compiler',
+ () async {
+ var suites = await loadSuitesWithConfig(suiteConfiguration(
+ compilerSelections: [CompilerSelection.parse('source')]));
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ suite = (await loadSuite.getSuite())!;
+ expect(suite.path, equals(p.join(d.sandbox, 'a_test.dart')));
+ expect(suite.platform.runtime, equals(Runtime.vm));
+ expect(suite.platform.compiler, equals(Compiler.source));
+ });
+
+ test('with multiple compiler selections, returns a suite for each',
+ () async {
+ var suites = await loadSuitesWithConfig(suiteConfiguration(
+ compilerSelections: [
+ CompilerSelection.parse('source'),
+ CompilerSelection.parse('kernel')
+ ]));
+
+ expect(suites, hasLength(2));
+ var runnerSuites =
+ await Future.wait([for (var suite in suites) suite.getSuite()]);
+ expect(
+ runnerSuites,
+ unorderedEquals([
+ isA<RunnerSuite>()
+ .having(
+ (s) => s.platform.runtime, 'The vm runtime', Runtime.vm)
+ .having((s) => s.platform.compiler, 'The source compiler',
+ Compiler.source),
+ isA<RunnerSuite>()
+ .having(
+ (s) => s.platform.runtime, 'The vm runtime', Runtime.vm)
+ .having((s) => s.platform.compiler, 'The kernel compiler',
+ Compiler.kernel),
+ ]));
+ });
+
+ test('with unsupported compiler selections, uses the default compiler',
+ () async {
+ var suites =
+ await loadSuitesWithConfig(suiteConfiguration(compilerSelections: [
+ CompilerSelection.parse('dart2js'),
+ ]));
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ suite = (await loadSuite.getSuite())!;
+ expect(suite.path, equals(p.join(d.sandbox, 'a_test.dart')));
+ expect(suite.platform.runtime, equals(Runtime.vm));
+ expect(suite.platform.compiler, equals(Runtime.vm.defaultCompiler));
+ });
+
+ test('compiler selections support matching boolean selectors', () async {
+ var suites =
+ await loadSuitesWithConfig(suiteConfiguration(compilerSelections: [
+ CompilerSelection.parse('vm:source'),
+ ]));
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ suite = (await loadSuite.getSuite())!;
+ expect(suite.path, equals(p.join(d.sandbox, 'a_test.dart')));
+ expect(suite.platform.runtime, equals(Runtime.vm));
+ expect(suite.platform.compiler, equals(Compiler.source));
+ });
+
+ test('compiler selections support unmatched boolean selectors', () async {
+ var suites =
+ await loadSuitesWithConfig(suiteConfiguration(compilerSelections: [
+ CompilerSelection.parse('browser:source'),
+ ]));
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ suite = (await loadSuite.getSuite())!;
+ expect(suite.path, equals(p.join(d.sandbox, 'a_test.dart')));
+ expect(suite.platform.runtime, equals(Runtime.vm));
+ expect(suite.platform.compiler,
+ allOf(Runtime.vm.defaultCompiler, isNot(Compiler.source)));
+ });
+ });
+ });
+
+ group('.loadDir()', () {
+ test('ignores non-Dart files', () async {
+ await d.file('a_test.txt', _tests).create();
+ expect(_loader.loadDir(d.sandbox, SuiteConfiguration.empty).toList(),
+ completion(isEmpty));
+ });
+
+ test("ignores files that don't end in _test.dart", () async {
+ await d.file('test.dart', _tests).create();
+ expect(_loader.loadDir(d.sandbox, SuiteConfiguration.empty).toList(),
+ completion(isEmpty));
+ });
+
+ group('with suites loaded from a directory', () {
+ late List<RunnerSuite> suites;
+ setUp(() async {
+ await d.file('a_test.dart', _tests).create();
+ await d.file('another_test.dart', _tests).create();
+ await d.dir('dir', [d.file('sub_test.dart', _tests)]).create();
+
+ suites = await _loader
+ .loadDir(d.sandbox, SuiteConfiguration.empty)
+ .asyncMap((loadSuite) async => (await loadSuite.getSuite())!)
+ .toList();
+ });
+
+ test('gives those suites the correct paths', () {
+ expect(
+ suites.map((suite) => suite.path),
+ unorderedEquals([
+ p.join(d.sandbox, 'a_test.dart'),
+ p.join(d.sandbox, 'another_test.dart'),
+ p.join(d.sandbox, 'dir', 'sub_test.dart')
+ ]));
+ });
+
+ test('can run tests in those suites', () {
+ var suite =
+ suites.firstWhere((suite) => suite.path!.contains('a_test'));
+ var liveTest = (suite.group.entries[1] as RunnerTest).load(suite);
+ expectSingleFailure(liveTest);
+ return liveTest.run().whenComplete(() => liveTest.close());
+ });
+ });
+ });
+
+ test('a print in a loaded file is piped through the LoadSuite', () async {
+ await d.file('a_test.dart', '''
+ void main() {
+ print('print within test');
+ }
+ ''').create();
+ var suites = await _loader
+ .loadFile(p.join(d.sandbox, 'a_test.dart'), SuiteConfiguration.empty)
+ .toList();
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+
+ var liveTest = (loadSuite.group.entries.single as Test).load(loadSuite);
+ expect(liveTest.onMessage.first.then((message) => message.text),
+ completion(equals('print within test')));
+ await liveTest.run();
+ expectTestPassed(liveTest);
+ });
+
+ group('LoadException', () {
+ test('suites can be retried', () async {
+ var numRetries = 5;
+
+ await d.file('a_test.dart', '''
+ import 'hello.dart';
+
+ void main() {}
+ ''').create();
+
+ var firstFailureCompleter = Completer<void>();
+
+ // After the first load failure we create the missing dependency.
+ unawaited(firstFailureCompleter.future.then((_) async {
+ await d.file('hello.dart', '''
+ String get message => 'hello';
+ ''').create();
+ }));
+
+ await runZoned(() async {
+ var suites = await _loader
+ .loadFile(p.join(d.sandbox, 'a_test.dart'),
+ suiteConfiguration(retry: numRetries))
+ .toList();
+ expect(suites, hasLength(1));
+ var loadSuite = suites.first;
+ var suite = (await loadSuite.getSuite())!;
+ expect(suite.path, equals(p.join(d.sandbox, 'a_test.dart')));
+ expect(suite.platform.runtime, equals(Runtime.vm));
+ }, zoneSpecification:
+ ZoneSpecification(print: (_, parent, zone, message) {
+ if (message.contains('Retrying load of') &&
+ !firstFailureCompleter.isCompleted) {
+ firstFailureCompleter.complete(null);
+ }
+ parent.print(zone, message);
+ }));
+
+ expect(firstFailureCompleter.isCompleted, true);
+ });
+ });
+
+ // TODO: Test load suites. Don't forget to test that prints in loaded files
+ // are piped through the suite. Also for browser tests!
+}
diff --git a/pkgs/test/test/runner/name_test.dart b/pkgs/test/test/runner/name_test.dart
new file mode 100644
index 0000000..bd4dc21
--- /dev/null
+++ b/pkgs/test/test/runner/name_test.dart
@@ -0,0 +1,460 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('with test.dart?name="name" query', () {
+ test('selects tests with matching names', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?name=selected']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('supports RegExp syntax', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {});
+ test("test 2", () => throw TestFailure("oh no"));
+ test("test 3", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?name=test [13]']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+
+ await test.shouldExit(0);
+ });
+
+ test('applies only to the associated file', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("selected 2", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ await d.file('test2.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(
+ ['test.dart?name=selected 1', 'test2.dart?name=selected 2'],
+ );
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+ await test.shouldExit(0);
+ });
+
+ test('selects more narrowly when passed multiple times', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?name=selected&name=1']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+ await test.shouldExit(0);
+ });
+
+ test('applies to directories', () async {
+ await d.dir('dir', [
+ d.file('first_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("selected 2", () => throw TestFailure("oh no"));
+ }
+ '''),
+ d.file('second_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("selected 2", () => throw TestFailure("oh no"));
+ }
+ ''')
+ ]).create();
+
+ var test = await runTest(['dir?name=selected 1']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+ await test.shouldExit(0);
+ });
+
+ test('produces an error when no tests match', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?name=no']);
+
+ expect(
+ test.stderr,
+ emitsThrough(contains('No tests were found.')),
+ );
+
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+
+ test("doesn't filter out load exceptions", () async {
+ var test = await runTest(['file?name=name']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading file [E]',
+ ' Failed to load "file": Does not exist.'
+ ]),
+ );
+
+ await test.shouldExit(1);
+ });
+ });
+
+ group('with test.dart?full-name query,', () {
+ test('matches with the complete test name', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected nope", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?full-name=selected']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+ await test.shouldExit(0);
+ });
+
+ test("doesn't support RegExp syntax", () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () => throw TestFailure("oh no"));
+ test("test 2", () => throw TestFailure("oh no"));
+ test("test [12]", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?full-name=test [12]']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+ await test.shouldExit(0);
+ });
+
+ test('applies only to the associated file', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("selected 2", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ await d.file('test2.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(
+ ['test.dart?full-name=selected 1', 'test2.dart?full-name=selected 2'],
+ );
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+2: All tests passed!')),
+ );
+ await test.shouldExit(0);
+ });
+
+ test('produces an error when no tests match', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?full-name=no match']);
+
+ expect(
+ test.stderr,
+ emitsThrough(contains('No tests were found.')),
+ );
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+ });
+
+ test('test?name="name" and --name narrow the selection', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope 1", () => throw TestFailure("oh no"));
+ test("selected 2", () => throw TestFailure("oh no"));
+ test("nope 2", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['--name', '1', 'test.dart?name=selected']);
+
+ expect(
+ test.stdout,
+ emitsThrough(contains('+1: All tests passed!')),
+ );
+ await test.shouldExit(0);
+ });
+
+ test('test?name="name" and test?full-name="name" throws', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope 1", () => throw TestFailure("oh no"));
+ test("selected 2", () => throw TestFailure("oh no"));
+ test("nope 2", () => throw TestFailure("oh no"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart?name=selected&full-name=selected 1']);
+
+ await test.shouldExit(64);
+ });
+
+ group('with the --name flag,', () {
+ test('selects tests with matching names', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['--name', 'selected', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('supports RegExp syntax', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {});
+ test("test 2", () => throw TestFailure("oh no"));
+ test("test 3", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['--name', 'test [13]', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('selects more narrowly when passed multiple times', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test =
+ await runTest(['--name', 'selected', '--name', '1', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('produces an error when no tests match', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['--name', 'no match', 'test.dart']);
+ expect(
+ test.stderr,
+ emitsThrough(
+ contains('No tests match regular expression "no match".')));
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+
+ test("doesn't filter out load exceptions", () async {
+ var test = await runTest(['--name', 'name', 'file']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading file [E]',
+ ' Failed to load "file": Does not exist.'
+ ]));
+ await test.shouldExit(1);
+ });
+ });
+
+ group('with the --plain-name flag,', () {
+ test('selects tests with matching names', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['--plain-name', 'selected', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't support RegExp syntax", () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () => throw TestFailure("oh no"));
+ test("test 2", () => throw TestFailure("oh no"));
+ test("test [12]", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['--plain-name', 'test [12]', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('selects more narrowly when passed multiple times', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test = await runTest(
+ ['--plain-name', 'selected', '--plain-name', '1', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('produces an error when no tests match', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['--plain-name', 'no match', 'test.dart']);
+ expect(test.stderr, emitsThrough(contains('No tests match "no match".')));
+ await test.shouldExit(exit_codes.noTestsRan);
+ });
+ });
+
+ test('--name and --plain-name together narrow the selection', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("selected 1", () {});
+ test("nope", () => throw TestFailure("oh no"));
+ test("selected 2", () {});
+ }
+ ''').create();
+
+ var test =
+ await runTest(['--name', '.....', '--plain-name', 'e', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/node/runner_test.dart b/pkgs/test/test/runner/node/runner_test.dart
new file mode 100644
index 0000000..82d2321
--- /dev/null
+++ b/pkgs/test/test/runner/node/runner_test.dart
@@ -0,0 +1,469 @@
+// 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', 'invalid Dart file').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 'package:js/js.dart';
+ import 'package:test/test.dart';
+
+ @JS("console.log")
+ external void log(value);
+
+ void main() {
+ test("test", () {
+ log("Hello,");
+ return Future(() => log("world!"));
+ });
+ }
+ ''').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 'package:js/js.dart';
+ import 'package:test/test.dart';
+
+ @JS()
+ external MyModule require(String name);
+
+ @JS()
+ class MyModule {
+ 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);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/parse_metadata_test.dart b/pkgs/test/test/runner/parse_metadata_test.dart
new file mode 100644
index 0000000..a8ffa9c
--- /dev/null
+++ b/pkgs/test/test/runner/parse_metadata_test.dart
@@ -0,0 +1,326 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/platform_selector.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_api/src/backend/suite_platform.dart';
+import 'package:test_core/src/runner/parse_metadata.dart';
+
+final _path = 'test.dart';
+
+void main() {
+ test('returns empty metadata for an empty file', () {
+ var metadata = parseMetadata(_path, '', {});
+ expect(metadata.testOn, equals(PlatformSelector.all));
+ expect(metadata.timeout.scaleFactor, equals(1));
+ });
+
+ test('ignores irrelevant annotations', () {
+ var metadata =
+ parseMetadata(_path, '@Fblthp\n@Fblthp.foo\nlibrary foo;', {});
+ expect(metadata.testOn, equals(PlatformSelector.all));
+ });
+
+ test('parses a prefixed annotation', () {
+ var metadata = parseMetadata(
+ _path,
+ "@foo.TestOn('vm')\n"
+ "import 'package:test/test.dart' as foo;",
+ {});
+ expect(metadata.testOn.evaluate(SuitePlatform(Runtime.vm, compiler: null)),
+ isTrue);
+ expect(
+ metadata.testOn.evaluate(SuitePlatform(Runtime.chrome, compiler: null)),
+ isFalse);
+ });
+
+ group('@TestOn:', () {
+ test('parses a valid annotation', () {
+ var metadata = parseMetadata(_path, "@TestOn('vm')\nlibrary foo;", {});
+ expect(
+ metadata.testOn.evaluate(SuitePlatform(Runtime.vm, compiler: null)),
+ isTrue);
+ expect(
+ metadata.testOn
+ .evaluate(SuitePlatform(Runtime.chrome, compiler: null)),
+ isFalse);
+ });
+
+ test('ignores a constructor named TestOn', () {
+ var metadata =
+ parseMetadata(_path, "@foo.TestOn('foo')\nlibrary foo;", {});
+ expect(metadata.testOn, equals(PlatformSelector.all));
+ });
+
+ group('throws an error for', () {
+ test('multiple @TestOns', () {
+ expect(
+ () => parseMetadata(
+ _path, "@TestOn('foo')\n@TestOn('bar')\nlibrary foo;", {}),
+ throwsFormatException);
+ });
+ });
+ });
+
+ group('@Timeout:', () {
+ test('parses a valid duration annotation', () {
+ var metadata = parseMetadata(_path, '''
+@Timeout(const Duration(
+ hours: 1,
+ minutes: 2,
+ seconds: 3,
+ milliseconds: 4,
+ microseconds: 5))
+
+library foo;
+''', {});
+ expect(
+ metadata.timeout.duration,
+ equals(const Duration(
+ hours: 1,
+ minutes: 2,
+ seconds: 3,
+ milliseconds: 4,
+ microseconds: 5)));
+ });
+
+ test('parses a valid duration omitting const', () {
+ var metadata = parseMetadata(_path, '''
+@Timeout(Duration(
+ hours: 1,
+ minutes: 2,
+ seconds: 3,
+ milliseconds: 4,
+ microseconds: 5))
+
+library foo;
+''', {});
+ expect(
+ metadata.timeout.duration,
+ equals(const Duration(
+ hours: 1,
+ minutes: 2,
+ seconds: 3,
+ milliseconds: 4,
+ microseconds: 5)));
+ });
+
+ test('parses a valid duration with an import prefix', () {
+ var metadata = parseMetadata(_path, '''
+@Timeout(core.Duration(
+ hours: 1,
+ minutes: 2,
+ seconds: 3,
+ milliseconds: 4,
+ microseconds: 5))
+import 'dart:core' as core;
+''', {});
+ expect(
+ metadata.timeout.duration,
+ equals(const Duration(
+ hours: 1,
+ minutes: 2,
+ seconds: 3,
+ milliseconds: 4,
+ microseconds: 5)));
+ });
+
+ test('parses a valid int factor annotation', () {
+ var metadata = parseMetadata(_path, '''
+@Timeout.factor(1)
+
+library foo;
+''', {});
+ expect(metadata.timeout.scaleFactor, equals(1));
+ });
+
+ test('parses a valid int factor annotation with an import prefix', () {
+ var metadata = parseMetadata(_path, '''
+@test.Timeout.factor(1)
+import 'package:test/test.dart' as test;
+''', {});
+ expect(metadata.timeout.scaleFactor, equals(1));
+ });
+
+ test('parses a valid double factor annotation', () {
+ var metadata = parseMetadata(_path, '''
+@Timeout.factor(0.5)
+
+library foo;
+''', {});
+ expect(metadata.timeout.scaleFactor, equals(0.5));
+ });
+
+ test('parses a valid Timeout.none annotation', () {
+ var metadata = parseMetadata(_path, '''
+@Timeout.none
+
+library foo;
+''', {});
+ expect(metadata.timeout, same(Timeout.none));
+ });
+
+ test('ignores a constructor named Timeout', () {
+ var metadata =
+ parseMetadata(_path, "@foo.Timeout('foo')\nlibrary foo;", {});
+ expect(metadata.timeout.scaleFactor, equals(1));
+ });
+
+ group('throws an error for', () {
+ test('multiple @Timeouts', () {
+ expect(
+ () => parseMetadata(_path,
+ '@Timeout.factor(1)\n@Timeout.factor(2)\nlibrary foo;', {}),
+ throwsFormatException);
+ });
+ });
+ });
+
+ group('@Skip:', () {
+ test('parses a valid annotation', () {
+ var metadata = parseMetadata(_path, '@Skip()\nlibrary foo;', {});
+ expect(metadata.skip, isTrue);
+ expect(metadata.skipReason, isNull);
+ });
+
+ test('parses a valid annotation with a reason', () {
+ var metadata = parseMetadata(_path, "@Skip('reason')\nlibrary foo;", {});
+ expect(metadata.skip, isTrue);
+ expect(metadata.skipReason, equals('reason'));
+ });
+
+ test('ignores a constructor named Skip', () {
+ var metadata = parseMetadata(_path, "@foo.Skip('foo')\nlibrary foo;", {});
+ expect(metadata.skip, isFalse);
+ });
+
+ group('throws an error for', () {
+ test('multiple @Skips', () {
+ expect(
+ () => parseMetadata(
+ _path, "@Skip('foo')\n@Skip('bar')\nlibrary foo;", {}),
+ throwsFormatException);
+ });
+ });
+ });
+
+ group('@Tags:', () {
+ test('parses a valid annotation', () {
+ var metadata = parseMetadata(_path, "@Tags(['a'])\nlibrary foo;", {});
+ expect(metadata.tags, equals(['a']));
+ });
+
+ test('ignores a constructor named Tags', () {
+ var metadata = parseMetadata(_path, "@foo.Tags(['a'])\nlibrary foo;", {});
+ expect(metadata.tags, isEmpty);
+ });
+
+ group('throws an error for', () {
+ test('multiple @Tags', () {
+ expect(
+ () => parseMetadata(
+ _path, "@Tags(['a'])\n@Tags(['b'])\nlibrary foo;", {}),
+ throwsFormatException);
+ });
+
+ test('String interpolation', () {
+ expect(
+ () => parseMetadata(
+ _path, "@Tags(['\$a'])\nlibrary foo;\nconst a = 'a';", {}),
+ throwsFormatException);
+ });
+ });
+ });
+
+ group('@OnPlatform:', () {
+ test('parses a valid annotation', () {
+ var metadata = parseMetadata(_path, '''
+@OnPlatform({
+ 'chrome': Timeout.factor(2),
+ 'vm': [Skip(), Timeout.factor(3)]
+})
+library foo;''', {});
+
+ var key = metadata.onPlatform.keys.first;
+ expect(
+ key.evaluate(SuitePlatform(Runtime.chrome, compiler: null)), isTrue);
+ expect(key.evaluate(SuitePlatform(Runtime.vm, compiler: null)), isFalse);
+ var value = metadata.onPlatform.values.first;
+ expect(value.timeout.scaleFactor, equals(2));
+
+ key = metadata.onPlatform.keys.last;
+ expect(key.evaluate(SuitePlatform(Runtime.vm, compiler: null)), isTrue);
+ expect(
+ key.evaluate(SuitePlatform(Runtime.chrome, compiler: null)), isFalse);
+ value = metadata.onPlatform.values.last;
+ expect(value.skip, isTrue);
+ expect(value.timeout.scaleFactor, equals(3));
+ });
+
+ test('parses a valid annotation with an import prefix', () {
+ var metadata = parseMetadata(_path, '''
+@test.OnPlatform({
+ 'chrome': test.Timeout.factor(2),
+ 'vm': [test.Skip(), test.Timeout.factor(3)]
+})
+import 'package:test/test.dart' as test;
+''', {});
+
+ var key = metadata.onPlatform.keys.first;
+ expect(
+ key.evaluate(SuitePlatform(Runtime.chrome, compiler: null)), isTrue);
+ expect(key.evaluate(SuitePlatform(Runtime.vm, compiler: null)), isFalse);
+ var value = metadata.onPlatform.values.first;
+ expect(value.timeout.scaleFactor, equals(2));
+
+ key = metadata.onPlatform.keys.last;
+ expect(key.evaluate(SuitePlatform(Runtime.vm, compiler: null)), isTrue);
+ expect(
+ key.evaluate(SuitePlatform(Runtime.chrome, compiler: null)), isFalse);
+ value = metadata.onPlatform.values.last;
+ expect(value.skip, isTrue);
+ expect(value.timeout.scaleFactor, equals(3));
+ });
+
+ test('ignores a constructor named OnPlatform', () {
+ var metadata =
+ parseMetadata(_path, "@foo.OnPlatform('foo')\nlibrary foo;", {});
+ expect(metadata.testOn, equals(PlatformSelector.all));
+ });
+
+ group('throws an error for', () {
+ test('a map with a unparseable key', () {
+ expect(
+ () => parseMetadata(
+ _path, "@OnPlatform({'invalid': Skip()})\nlibrary foo;", {}),
+ throwsFormatException);
+ });
+
+ test('a map with an invalid value', () {
+ expect(
+ () => parseMetadata(_path,
+ "@OnPlatform({'vm': const TestOn('vm')})\nlibrary foo;", {}),
+ throwsFormatException);
+ });
+
+ test('a map with an invalid value in a list', () {
+ expect(
+ () => parseMetadata(_path,
+ "@OnPlatform({'vm': [const TestOn('vm')]})\nlibrary foo;", {}),
+ throwsFormatException);
+ });
+
+ test('multiple @OnPlatforms', () {
+ expect(
+ () => parseMetadata(
+ _path, '@OnPlatform({})\n@OnPlatform({})\nlibrary foo;', {}),
+ throwsFormatException);
+ });
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/pause_after_load_test.dart b/pkgs/test/test/runner/pause_after_load_test.dart
new file mode 100644
index 0000000..faba441
--- /dev/null
+++ b/pkgs/test/test/runner/pause_after_load_test.dart
@@ -0,0 +1,283 @@
+// Copyright (c) 2015, 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')
+@OnPlatform({'windows': Skip('https://github.com/dart-lang/test/issues/1613')})
+library;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('pauses the test runner for each file until the user presses enter',
+ () async {
+ await d.file('test1.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ print('loaded test 1!');
+
+ test("success", () {});
+}
+''').create();
+
+ await d.file('test2.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+
+ print('loaded test 2!');
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(
+ ['--pause-after-load', '-p', 'chrome', 'test1.dart', 'test2.dart']);
+ await expectLater(test.stdout, emitsThrough('loaded test 1!'));
+ await expectLater(test.stdout, emitsThrough(equalsIgnoringWhitespace('''
+ The test runner is paused. Open the dev console in Chrome and set
+ breakpoints. Once you're finished, return to this terminal and press
+ Enter.
+ ''')));
+
+ var nextLineFired = false;
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+0: test1.dart: success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+
+ await expectLater(test.stdout, emitsThrough('loaded test 2!'));
+ await expectLater(test.stdout, emitsThrough(equalsIgnoringWhitespace('''
+ The test runner is paused. Open the dev console in Chrome and set
+ breakpoints. Once you're finished, return to this terminal and press
+ Enter.
+ ''')));
+
+ nextLineFired = false;
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+1: test2.dart: success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+ await expectLater(
+ test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test('pauses the test runner for each platform until the user presses enter',
+ () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ print('loaded test!');
+
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest([
+ '--pause-after-load',
+ '-p',
+ 'firefox',
+ '-p',
+ 'chrome',
+ '-p',
+ 'vm',
+ 'test.dart'
+ ]);
+ await expectLater(test.stdout, emitsThrough('loaded test!'));
+ await expectLater(test.stdout, emitsThrough(equalsIgnoringWhitespace('''
+ The test runner is paused. Open the dev console in Firefox and set
+ breakpoints. Once you're finished, return to this terminal and press
+ Enter.
+ ''')));
+
+ var nextLineFired = false;
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+0: [Firefox, Dart2Js] success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+
+ await expectLater(test.stdout, emitsThrough('loaded test!'));
+ await expectLater(
+ test.stdout,
+ emitsThrough(emitsInOrder([
+ 'The test runner is paused. Open the dev console in Chrome and set '
+ "breakpoints. Once you're finished, return to this terminal and "
+ 'press Enter.'
+ ])));
+
+ nextLineFired = false;
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+1: [Chrome, Dart2Js] success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+ await expectLater(test.stdout, emitsThrough('loaded test!'));
+ await expectLater(
+ test.stdout,
+ emitsThrough(emitsInOrder([
+ 'The test runner is paused. Open the Observatory and set '
+ "breakpoints. Once you're finished, return to this terminal "
+ 'and press Enter.'
+ ])));
+
+ nextLineFired = false;
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+2: [VM, Kernel] success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+
+ await expectLater(
+ test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: ['firefox', 'chrome', 'vm']);
+
+ test('stops immediately if killed while paused', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ print('loaded test!');
+
+ test("success", () {});
+}
+''').create();
+
+ var test =
+ await runTest(['--pause-after-load', '-p', 'chrome', 'test.dart']);
+ await expectLater(test.stdout, emitsThrough('loaded test!'));
+ await expectLater(test.stdout, emitsThrough(equalsIgnoringWhitespace('''
+ The test runner is paused. Open the dev console in Chrome and set
+ breakpoints. Once you're finished, return to this terminal and press
+ Enter.
+ ''')));
+
+ test.signal(ProcessSignal.sigterm);
+ await test.shouldExit();
+ await expectLater(test.stderr, emitsDone);
+ }, tags: 'chrome', testOn: '!windows');
+
+ test('disables timeouts', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ print('loaded test 1!');
+
+ test("success", () async {
+ await Future.delayed(Duration.zero);
+ }, timeout: Timeout(Duration.zero));
+}
+''').create();
+
+ var test = await runTest(
+ ['--pause-after-load', '-p', 'chrome', '-n', 'success', 'test.dart']);
+ await expectLater(test.stdout, emitsThrough('loaded test 1!'));
+ await expectLater(test.stdout, emitsThrough(equalsIgnoringWhitespace('''
+ The test runner is paused. Open the dev console in Chrome and set
+ breakpoints. Once you're finished, return to this terminal and press
+ Enter.
+ ''')));
+
+ var nextLineFired = false;
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+0: success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+ await expectLater(
+ test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ // Regression test for #304.
+ test('supports test name patterns', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ print('loaded test 1!');
+
+ test("failure 1", () {});
+ test("success", () {});
+ test("failure 2", () {});
+}
+''').create();
+
+ var test = await runTest(
+ ['--pause-after-load', '-p', 'chrome', '-n', 'success', 'test.dart']);
+ await expectLater(test.stdout, emitsThrough('loaded test 1!'));
+ await expectLater(test.stdout, emitsThrough(equalsIgnoringWhitespace('''
+ The test runner is paused. Open the dev console in Chrome and set
+ breakpoints. Once you're finished, return to this terminal and press
+ Enter.
+ ''')));
+
+ var nextLineFired = false;
+ unawaited(test.stdout.next.then(expectAsync1((line) {
+ expect(line, contains('+0: success'));
+ nextLineFired = true;
+ })));
+
+ // Wait a little bit to be sure that the tests don't start running without
+ // our input.
+ await Future<void>.delayed(const Duration(seconds: 2));
+ expect(nextLineFired, isFalse);
+
+ test.stdin.writeln();
+ await expectLater(
+ test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+}
diff --git a/pkgs/test/test/runner/precompiled_test.dart b/pkgs/test/test/runner/precompiled_test.dart
new file mode 100644
index 0000000..51dde28
--- /dev/null
+++ b/pkgs/test/test/runner/precompiled_test.dart
@@ -0,0 +1,264 @@
+// Copyright (c) 2016, 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')
+@OnPlatform({'windows': Skip('https://github.com/dart-lang/test/issues/1617')})
+library;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:node_preamble/preamble.dart' as preamble;
+import 'package:package_config/package_config.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('browser tests', () {
+ setUpAll(() async {
+ await _precompileBrowserTest('test.dart');
+ });
+
+ test('run a precompiled version of a test rather than recompiling',
+ () async {
+ var test = await runTest([
+ '-p',
+ 'chrome',
+ '--precompiled=precompiled/',
+ 'test.dart',
+ ]);
+ expect(test.stdout,
+ containsInOrder(['+0: success', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test('run two precompiled tests', () async {
+ await _precompileBrowserTest('test_2.dart');
+ var test = await runTest(concurrency: 2, [
+ '-p',
+ 'chrome',
+ '--precompiled=precompiled/',
+ 'test.dart',
+ 'test_2.dart',
+ ]);
+ expect(test.stdout, containsInOrder(['+2: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test('can use the json reporter', () async {
+ var test = await runTest([
+ '-p',
+ 'chrome',
+ '--precompiled=precompiled/',
+ 'test.dart',
+ '-r',
+ 'json'
+ ]);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '{"testID":3,"result":"success"',
+ '{"success":true,"type":"done"'
+ ]));
+ await test.shouldExit(0);
+ });
+ }, tags: const ['chrome']);
+
+ group('node tests', () {
+ setUp(() async {
+ await d.dir('test', [
+ d.file('test.dart', '''
+ import "package:test/src/bootstrap/node.dart";
+ import "package:test/test.dart";
+
+ void main() {
+ internalBootstrapNodeTest(() => () => test("success", () {
+ expect(true, isTrue);
+ }));
+ }''')
+ ]).create();
+ await _writePackagesFile();
+
+ var jsPath = p.join(d.sandbox, 'test', 'test.dart.node_test.dart.js');
+ var dart2js = await TestProcess.start(
+ Platform.resolvedExecutable,
+ [
+ 'compile',
+ 'js',
+ '--packages=${await Isolate.packageConfig}',
+ p.join('test', 'test.dart'),
+ '--out=$jsPath',
+ ],
+ workingDirectory: d.sandbox);
+ await dart2js.shouldExit(0);
+
+ var jsFile = File(jsPath);
+ await jsFile.writeAsString(
+ preamble.getPreamble(minified: true) + await jsFile.readAsString());
+
+ await d.dir('test', [d.file('test.dart', 'invalid dart}')]).create();
+ });
+
+ test('run a precompiled version of a test rather than recompiling',
+ () async {
+ var test = await runTest([
+ '-p',
+ 'node',
+ '--precompiled',
+ d.sandbox,
+ p.join('test', 'test.dart')
+ ]);
+ expect(test.stdout,
+ containsInOrder(['+0: success', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test('can use the json reporter', () async {
+ var test = await runTest([
+ '-p',
+ 'node',
+ '--precompiled',
+ d.sandbox,
+ p.join('test', 'test.dart'),
+ '-r',
+ 'json'
+ ]);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '{"testID":3,"result":"success"',
+ '{"success":true,"type":"done"'
+ ]));
+ await test.shouldExit(0);
+ });
+ }, tags: const ['node']);
+
+ group('vm tests', () {
+ setUp(() async {
+ await d.dir('test', [
+ d.file('test.dart', '''
+ import "package:test/test.dart";
+ void main() {
+ test("true is true", () {
+ expect(true, isTrue);
+ });
+ }
+ '''),
+ d.file('test.dart.vm_test.dart', '''
+ import "dart:isolate";
+ import "package:test_core/src/bootstrap/vm.dart";
+ import "test.dart" as test;
+ void main(_, SendPort message) {
+ internalBootstrapVmTest(() => test.main, message);
+ }
+ '''),
+ ]).create();
+ await _writePackagesFile();
+ });
+
+ test('run in the precompiled directory', () async {
+ var test = await runTest(
+ ['-p', 'vm', '--precompiled=${d.sandbox}', 'test/test.dart']);
+ expect(test.stdout,
+ containsInOrder(['+0: true is true', '+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+
+ test('can load precompiled dill files if available', () async {
+ // Create the snapshot in the sandbox directory.
+ var snapshotProcess = await runDart([
+ '--snapshot_kind=script',
+ '--snapshot=test/test.dart.vm_test.vm.app.dill',
+ 'test/test.dart.vm_test.dart'
+ ]);
+ await snapshotProcess.shouldExit(0);
+
+ // Modify the original test so it would fail if it actually got ran, this
+ // makes sure the test fails if the dill file isn't loaded.
+ var testFile = File(p.join(d.sandbox, 'test', 'test.dart'));
+ expect(await testFile.exists(), isTrue);
+ var originalContent = await testFile.readAsString();
+ await testFile
+ .writeAsString(originalContent.replaceAll('isTrue', 'isFalse'));
+
+ // Actually invoke the test with the dill file.
+ var testProcess = await runTest(
+ ['-p', 'vm', '--precompiled=${d.sandbox}', 'test/test.dart']);
+ expect(testProcess.stdout,
+ containsInOrder(['+0: true is true', '+1: All tests passed!']));
+ await testProcess.shouldExit(0);
+ });
+
+ test('can use the json reporter', () async {
+ var test = await runTest([
+ '-p',
+ 'vm',
+ '--precompiled=${d.sandbox}',
+ 'test/test.dart',
+ '-r',
+ 'json'
+ ]);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '{"testID":3,"result":"success"',
+ '{"success":true,"type":"done"'
+ ]));
+ await test.shouldExit(0);
+ });
+ });
+}
+
+Future<void> _writePackagesFile() async {
+ var config = (await findPackageConfig(Directory.current))!;
+ await d.dir('.dart_tool').create();
+ await savePackageConfig(config, Directory(d.sandbox));
+}
+
+Future<void> _precompileBrowserTest(String testPath) async {
+ var tmpDir = await Directory.systemTemp.createTemp('browser_test');
+ var file = File.fromUri(tmpDir.uri.resolve('precompiled.dart'));
+ await file.writeAsString('''
+ import "package:test/bootstrap/browser.dart";
+ import "package:test/test.dart";
+
+ main(_) {
+ internalBootstrapBrowserTest(() => () => test("success", () {}));
+ }
+ ''');
+
+ await d.dir('precompiled', [
+ d.file(p.setExtension(testPath, 'html'), '''
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>test Test</title>
+ <script src="$testPath.browser_test.dart.js"></script>
+ </head>
+ </html>
+ ''')
+ ]).create();
+
+ var dart2js = await TestProcess.start(
+ Platform.resolvedExecutable,
+ [
+ 'compile',
+ 'js',
+ ...Platform.executableArguments,
+ '--packages=${(await Isolate.packageConfig)!.toFilePath()}',
+ file.path,
+ '--out=precompiled/$testPath.browser_test.dart.js'
+ ],
+ workingDirectory: d.sandbox);
+ await dart2js.shouldExit(0);
+
+ await d.file(testPath, 'invalid dart}').create();
+}
diff --git a/pkgs/test/test/runner/retry_test.dart b/pkgs/test/test/runner/retry_test.dart
new file mode 100644
index 0000000..0e16fef
--- /dev/null
+++ b/pkgs/test/test/runner/retry_test.dart
@@ -0,0 +1,231 @@
+// 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('respects --no-retry flag with retry option', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ var attempt = 0;
+ void main() {
+ test("eventually passes", () {
+ attempt++;
+ if(attempt <= 1 ) {
+ throw TestFailure("oh no");
+ }
+ }, retry: 1);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart', '--no-retry']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('respects --no-retry flag with @Retry declaration', () async {
+ await d.file('test.dart', '''
+ @Retry(3)
+
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ var attempt = 0;
+ void main() {
+ test("eventually passes", () {
+ attempt++;
+ if(attempt <= 1 ) {
+ throw TestFailure("oh no");
+ }
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart', '--no-retry']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('respects top-level @Retry declarations', () async {
+ await d.file('test.dart', '''
+ @Retry(3)
+
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ var attempt = 0;
+ void main() {
+ test("failure", () {
+ attempt++;
+ if(attempt <= 3) {
+ throw TestFailure("oh no");
+ }
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('respects group retry declarations', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ var attempt = 0;
+ void main() {
+ group("retry", () {
+ test("failure", () {
+ attempt++;
+ if(attempt <= 3) {
+ throw TestFailure("oh no");
+ }
+ });
+ }, retry: 3);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('tests are not retried after they have already been reported successful',
+ () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ var completer1 = Completer();
+ var completer2 = Completer();
+ test("first", () {
+ completer1.future.then((_) {
+ completer2.complete();
+ throw "oh no";
+ });
+ }, retry: 2);
+
+ test("second", () async {
+ completer1.complete();
+ await completer2.future;
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ contains('This test failed after it had already completed')));
+ await test.shouldExit(1);
+ });
+
+ group('retries tests', () {
+ test('and eventually passes for valid tests', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ var attempt = 0;
+ void main() {
+ test("eventually passes", () {
+ attempt++;
+ if(attempt <= 2) {
+ throw TestFailure("oh no");
+ }
+ }, retry: 2);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('and ignores previous errors', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ var attempt = 0;
+ Completer completer = Completer();
+ void main() {
+ test("failure", () async {
+ attempt++;
+ if (attempt == 1) {
+ completer.future.then((_) => throw 'some error');
+ throw TestFailure("oh no");
+ }
+ completer.complete(null);
+ await Future((){});
+ }, retry: 1);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('and eventually fails for invalid tests', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () {
+ throw TestFailure("oh no");
+ }, retry: 2);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('only after a failure', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ var attempt = 0;
+ void main() {
+ test("eventually passes", () {
+ attempt++;
+ if (attempt != 2){
+ throw TestFailure("oh no");
+ }
+ }, retry: 5);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/runner_test.dart b/pkgs/test/test/runner/runner_test.dart
new file mode 100644
index 0000000..379e2a7
--- /dev/null
+++ b/pkgs/test/test/runner/runner_test.dart
@@ -0,0 +1,871 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:io';
+import 'dart:math' as math;
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+final _success = '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''';
+
+final _failure = '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () => throw TestFailure("oh no"));
+}
+''';
+
+final _asyncFailure = '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("failure", () async {
+ await Future(() {}).then((_) {
+ throw 'oh no';
+ });
+ });
+}
+''';
+
+final _defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2);
+
+final _usage = '''
+Usage: dart test [files or directories...]
+
+-h, --help Show this usage information.
+ --version Show the package:test version.
+
+Selecting Tests:
+-n, --name A substring of the name of the test to run.
+ Regular expression syntax is supported.
+ If passed multiple times, tests must match all substrings.
+-N, --plain-name A plain-text substring of the name of the test to run.
+ If passed multiple times, tests must match all substrings.
+-t, --tags Run only tests with all of the specified tags.
+ Supports boolean selector syntax.
+-x, --exclude-tags Don't run tests with any of the specified tags.
+ Supports boolean selector syntax.
+ --[no-]run-skipped Run skipped tests instead of skipping them.
+
+Running Tests:
+-p, --platform The platform(s) on which to run the tests.
+ $_runtimes.
+ Each platform supports the following compilers:
+$_runtimeCompilers
+-c, --compiler The compiler(s) to use to run tests, supported compilers are [dart2js, dart2wasm, exe, kernel, source].
+ Each platform has a default compiler but may support other compilers.
+ You can target a compiler to a specific platform using arguments of the following form [<platform-selector>:]<compiler>.
+ If a platform is specified but no given compiler is supported for that platform, then it will use its default compiler.
+-P, --preset The configuration preset(s) to use.
+-j, --concurrency=<threads> The number of concurrent test suites run.
+ (defaults to "$_defaultConcurrency")
+ --total-shards The total number of invocations of the test runner being run.
+ --shard-index The index of this test runner invocation (of --total-shards).
+ --timeout The default test timeout. For example: 15s, 2x, none
+ (defaults to "30s")
+ --ignore-timeouts Ignore all timeouts (useful if debugging)
+ --pause-after-load Pause for debugging before any tests execute.
+ Implies --concurrency=1, --debug, and --ignore-timeouts.
+ Currently only supported for browser tests.
+ --debug Run the VM and Chrome tests in debug mode.
+ --coverage=<directory> Gather coverage and output it to the specified directory.
+ Implies --debug.
+ --[no-]chain-stack-traces Use chained stack traces to provide greater exception details
+ especially for asynchronous code. It may be useful to disable
+ to provide improved test performance but at the cost of
+ debuggability.
+ --no-retry Don't rerun tests that have retry set.
+ --test-randomize-ordering-seed Use the specified seed to randomize the execution order of test cases.
+ Must be a 32bit unsigned integer or "random".
+ If "random", pick a random seed to use.
+ If not passed, do not randomize test case execution order.
+ --[no-]fail-fast Stop running tests after the first failure.
+
+Output:
+-r, --reporter=<option> Set how to print test results.
+
+ [compact] A single line, updated continuously.
+ [expanded] (default) A separate line for each update.
+ [failures-only] A separate line for failing tests with no output for passing tests
+ [github] A custom reporter for GitHub Actions (the default reporter when running on GitHub Actions).
+ [json] A machine-readable format (see https://dart.dev/go/test-docs/json_reporter.md).
+ [silent] A reporter with no output. May be useful when only the exit code is meaningful.
+
+ --file-reporter Enable an additional reporter writing test results to a file.
+ Should be in the form <reporter>:<filepath>, Example: "json:reports/tests.json"
+ --verbose-trace Emit stack traces with core library frames.
+ --js-trace Emit raw JavaScript stack traces for browser tests.
+ --[no-]color Use terminal colors.
+ (auto-detected by default)
+''';
+
+final _runtimes = '[vm (default), chrome, firefox'
+ '${Platform.isMacOS ? ', safari' : ''}'
+ ', edge, node]';
+
+final _runtimeCompilers = [
+ '[vm]: kernel (default), source, exe',
+ '[chrome]: dart2js (default), dart2wasm',
+ '[firefox]: dart2js (default), dart2wasm',
+ if (Platform.isMacOS) '[safari]: dart2js (default)',
+ '[edge]: dart2js (default)',
+ '[node]: dart2js (default), dart2wasm',
+].map((str) => ' $str').join('\n');
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('prints help information', () async {
+ var test = await runTest(['--help']);
+ expectStdoutEquals(test, '''
+Runs tests in this package.
+
+$_usage''');
+ await test.shouldExit(0);
+ });
+
+ group('fails gracefully if', () {
+ test('an invalid option is passed', () async {
+ var test = await runTest(['--asdf']);
+ expectStderrEquals(test, '''
+Could not find an option named "--asdf".
+
+$_usage''');
+ await test.shouldExit(exit_codes.usage);
+ });
+
+ test('a non-existent file is passed', () async {
+ var test = await runTest(['file']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading file [E]',
+ 'Failed to load "file": Does not exist.'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test("the default directory doesn't exist", () async {
+ var test = await runTest([]);
+ expectStderrEquals(test, '''
+No test files were passed and the default "test/" directory doesn't exist.
+
+$_usage''');
+ await test.shouldExit(exit_codes.data);
+ });
+
+ test('a test file fails to load', () async {
+ await d.file('test.dart', 'invalid Dart file').create();
+ var test = await runTest(['test.dart']);
+
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Failed to load "test.dart":',
+ "test.dart:1:9: Error: Expected ';' after this.",
+ 'invalid Dart file'
+ ]));
+
+ await test.shouldExit(1);
+ });
+
+ // This syntax error is detected lazily, and so requires some extra
+ // machinery to support.
+ test('a test file fails to parse due to a missing semicolon', () async {
+ await d.file('test.dart', 'void main() {foo}').create();
+ var test = await runTest(['test.dart']);
+
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart":',
+ "test.dart:1:14: Error: Expected ';' after this"
+ ]));
+
+ await test.shouldExit(1);
+ });
+
+ // This is slightly different from the above test because it's an error
+ // that's caught first by the analyzer when it's used to parse the file.
+ test('a test file fails to parse', () async {
+ await d.file('test.dart', '@TestOn)').create();
+ var test = await runTest(['test.dart']);
+
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart":',
+ "test.dart:1:8: Error: Expected a declaration, but got ')'",
+ '@TestOn)',
+ ]));
+
+ await test.shouldExit(1);
+ });
+
+ test("an annotation's contents are invalid", () async {
+ await d.file('test.dart', "@TestOn('zim')\nlibrary foo;").create();
+ var test = await runTest(['test.dart']);
+
+ expect(
+ test.stdout,
+ containsInOrder([
+ '-1: loading test.dart [E]',
+ 'Failed to load "test.dart":',
+ 'Error on line 1, column 10: Undefined variable.',
+ "@TestOn('zim')",
+ ' ^^^'
+ ]));
+ 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(['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(['test.dart']);
+
+ expect(
+ test.stdout,
+ emitsThrough(
+ contains('-1: loading test.dart [E]'),
+ ));
+ expect(
+ test.stdout,
+ emitsThrough(anyOf([
+ contains("Error: Getter not found: 'main'"),
+ contains("Error: Undefined name 'main'"),
+ ])));
+
+ await test.shouldExit(1);
+ });
+
+ test('a test file has a non-function main', () async {
+ await d.file('test.dart', 'int main = 0;').create();
+ var test = await runTest(['test.dart']);
+
+ expect(test.stdout, emitsThrough(contains('-1: loading test.dart [E]')));
+ expect(
+ test.stdout,
+ emitsThrough(anyOf([
+ contains(
+ "A value of type 'int' can't be assigned to a variable of type "
+ "'Function'",
+ ),
+ contains(
+ "A value of type 'int' can't be returned from a function with "
+ "return type 'Function'",
+ ),
+ ])));
+
+ await test.shouldExit(1);
+ });
+
+ test('a test file has a main with arguments', () async {
+ await d.file('test.dart', 'void main(arg) {}').create();
+ var test = await runTest(['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);
+ });
+
+ test('multiple load errors occur', () async {
+ await d.file('test.dart', 'invalid Dart file').create();
+ var test = await runTest(['test.dart', 'nonexistent.dart']);
+
+ expect(
+ await test.stdoutStream().toList(),
+ containsAll([
+ contains('loading nonexistent.dart [E]'),
+ contains('Failed to load "nonexistent.dart": Does not exist'),
+ contains('loading test.dart [E]'),
+ contains('Failed to load "test.dart"'),
+ ]));
+
+ await test.shouldExit(1);
+ });
+
+ // TODO(nweiz): test what happens when a test file is unreadable once issue
+ // 15078 is fixed.
+ });
+
+ group('runs successful tests', () {
+ test('defined in a single file', () async {
+ await d.file('test.dart', _success).create();
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('defined in a directory', () async {
+ for (var i = 0; i < 3; i++) {
+ await d.file('${i}_test.dart', _success).create();
+ }
+
+ var test = await runTest(['.']);
+ expect(test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('defaulting to the test directory', () async {
+ await d
+ .dir(
+ 'test',
+ Iterable.generate(3, (i) {
+ return d.file('${i}_test.dart', _success);
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('directly', () async {
+ await d.file('test.dart', _success).create();
+ var test = await runDart(['test.dart']);
+
+ expect(test.stdout, emitsThrough(contains('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(['dir/test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('given a file: uri', () async {
+ await d.file('test.dart', _success).create();
+ var fileUri = p.toUri(d.path('test.dart')).toString();
+ expect(fileUri, startsWith('file:///'));
+ var test = await runTest([fileUri]);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('with platform specific relative paths', () async {
+ await d.dir('foo', [d.file('test.dart', _success)]).create();
+ var test = await runTest([p.join('foo', 'test.dart')]);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('with platform specific absolute paths', () async {
+ await d.dir('foo', [d.file('test.dart', _success)]).create();
+ var test = await runTest([d.path(p.join('foo', 'test.dart'))]);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('with platform specific relative paths containing query params',
+ () async {
+ await d.dir('foo', [d.file('test.dart', _success)]).create();
+ var test = await runTest(['${p.join('foo', 'test.dart')}?line=6']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+ });
+
+ group('runs successful tests with async setup', () {
+ setUp(() async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() async {
+ test("success 1", () {});
+
+ await () async {};
+
+ test("success 2", () {});
+ }
+ ''').create();
+ });
+
+ test('defined in a single file', () async {
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('directly', () async {
+ var test = await runDart(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+ });
+
+ group('runs failing tests', () {
+ test('respects the chain-stack-traces flag', () async {
+ await d.file('test.dart', _asyncFailure).create();
+
+ var test = await runTest(['test.dart', '--chain-stack-traces']);
+ expect(test.stdout, emitsThrough(contains('asynchronous gap')));
+ await test.shouldExit(1);
+ });
+
+ test('defaults to not chaining stack traces', () async {
+ await d.file('test.dart', _asyncFailure).create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '00:00 +0: failure',
+ '00:00 +0 -1: failure [E]',
+ 'oh no',
+ 'test.dart 8:7 main.<fn>.<fn>',
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('defined in a single file', () async {
+ await d.file('test.dart', _failure).create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('defined in a directory', () async {
+ for (var i = 0; i < 3; i++) {
+ await d.file('${i}_test.dart', _failure).create();
+ }
+
+ var test = await runTest(['.']);
+ expect(test.stdout, emitsThrough(contains('-3: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('defaulting to the test directory', () async {
+ await d
+ .dir(
+ 'test',
+ Iterable.generate(3, (i) {
+ return d.file('${i}_test.dart', _failure);
+ }))
+ .create();
+
+ var test = await runTest([]);
+ expect(test.stdout, emitsThrough(contains('-3: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test('directly', () async {
+ await d.file('test.dart', _failure).create();
+ var test = await runDart(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('Some tests failed.')));
+ await test.shouldExit(255);
+ });
+ });
+
+ test('runs tests even when a file fails to load', () async {
+ await d.file('test.dart', _success).create();
+
+ var test = await runTest(['test.dart', 'nonexistent.dart']);
+ expect(test.stdout, emitsThrough(contains('+1 -1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ group('with a top-level @Skip declaration', () {
+ setUp(() async {
+ await d.file('test.dart', '''
+ @Skip()
+
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test('success', () {});
+ test('explicitly unskipped', skip: false, () {});
+ }
+ ''').create();
+ });
+
+ test('skips all tests', () async {
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+0 ~1: All tests skipped.')));
+ await test.shouldExit(0);
+ });
+
+ test('runs all tests with --run-skipped', () async {
+ var test = await runTest(['--run-skipped', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('+2: 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: {"vm": Skip()});
+}
+''').create();
+
+ var test = await runTest(['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: {"chrome": Skip()});
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('respects matching Timeouts', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("fail", () async {
+ await Future.delayed(Duration.zero);
+ throw 'oh no';
+ }, onPlatform: {
+ "vm": Timeout(Duration.zero)
+ });
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('ignores non-matching Timeouts', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {}, onPlatform: {
+ "chrome": Timeout(Duration(seconds: 0))
+ });
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('applies matching platforms in order', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {}, onPlatform: {
+ "vm": Skip("first"),
+ "vm || windows": Skip("second"),
+ "vm || linux": Skip("third"),
+ "vm || mac-os": Skip("fourth"),
+ "vm || android": Skip("fifth")
+ });
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdoutStream(), neverEmits(contains('Skip: first')));
+ expect(test.stdoutStream(), neverEmits(contains('Skip: second')));
+ expect(test.stdoutStream(), neverEmits(contains('Skip: third')));
+ expect(test.stdoutStream(), neverEmits(contains('Skip: fourth')));
+ expect(test.stdout, emitsThrough(contains('Skip: fifth')));
+ await test.shouldExit(0);
+ });
+
+ test('applies platforms to a group', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ group("group", () {
+ test("success", () {});
+ }, onPlatform: {
+ "vm": Skip()
+ });
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests skipped.')));
+ await test.shouldExit(0);
+ });
+ });
+
+ group('with an @OnPlatform annotation', () {
+ test('respects matching Skips', () async {
+ await d.file('test.dart', '''
+@OnPlatform(const {"vm": const Skip()})
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("fail", () => throw 'oh no');
+}
+''').create();
+
+ var test = await runTest(['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', '''
+@OnPlatform(const {"chrome": const Skip()})
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('respects matching Timeouts', () async {
+ await d.file('test.dart', '''
+@OnPlatform(const {
+ "vm": const Timeout(const Duration(seconds: 0))
+})
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("fail", () async {
+ await Future.delayed(Duration.zero);
+ throw 'oh no';
+ });
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('ignores non-matching Timeouts', () async {
+ await d.file('test.dart', '''
+@OnPlatform(const {
+ "chrome": const Timeout(const Duration(seconds: 0))
+})
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("success", () {});
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+ });
+
+ test('with the --color flag, uses colors', () async {
+ await d.file('test.dart', _failure).create();
+ var test = await runTest(['--color', 'test.dart']);
+ // This is the color code for red.
+ expect(test.stdout, emitsThrough(contains('\u001b[31m')));
+ await test.shouldExit();
+ });
+
+ group('runs tests successfully more than once when calling runTests', () {
+ test('defined in a single file', () async {
+ await d.file('test.dart', _success).create();
+ await d.file('runner.dart', '''
+import 'package:test_core/src/executable.dart' as test;
+
+void main(List<String> args) async {
+ await test.runTests(args);
+ await test.runTests(args);
+ test.completeShutdown();
+}''').create();
+ var test = await runDart([
+ 'runner.dart',
+ '--no-color',
+ '--reporter',
+ 'compact',
+ '--',
+ 'test.dart',
+ ], description: 'dart runner.dart -- test.dart');
+ expect(
+ test.stdout,
+ emitsThrough(containsInOrder([
+ '+0: loading test.dart',
+ '+0: success',
+ '+1: success',
+ 'All tests passed!'
+ ])));
+ expect(
+ test.stdout,
+ emitsThrough(containsInOrder([
+ '+0: loading test.dart',
+ '+0: success',
+ '+1: success',
+ '+1: All tests passed!',
+ ])));
+ await test.shouldExit(0);
+ });
+ }, onPlatform: const {
+ 'windows': Skip('https://github.com/dart-lang/test/issues/1615')
+ });
+
+ group('language experiments', () {
+ group('are inherited from the executable arguments', () {
+ setUp(() async {
+ await d.file('test.dart', '''
+// @dart=2.10
+import 'package:test/test.dart';
+
+// Compile time error if the experiment is enabled
+int x;
+
+void main() {
+ test('x is null', () {
+ expect(x, isNull);
+ });
+}
+''').create();
+ });
+
+ for (var platform in ['vm', 'chrome']) {
+ test('on the $platform platform', () async {
+ var test = await runTest(['test.dart', '-p', platform],
+ vmArgs: ['--enable-experiment=non-nullable']);
+
+ await expectLater(test.stdout, emitsThrough(contains('int x;')));
+ await test.shouldExit(1);
+
+ // Test that they can be removed on subsequent runs as well
+ test = await runTest(['test.dart', '-p', platform]);
+ await expectLater(
+ test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ }, skip: 'https://github.com/dart-lang/test/issues/1813');
+ }
+ });
+ });
+
+ group('runs tests after changing directories', () {
+ setUp(() async {
+ await d.file('a_test.dart', '''
+@TestOn('vm')
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+void main() {
+ test('changes directory', () {
+ Directory.current = Directory.current.parent;
+ });
+}
+''').create();
+ await d.file('b_test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test('passes', () {
+ expect(true, true);
+ });
+}
+''').create();
+ });
+ test('on the VM platform', () async {
+ var test = await runTest(['-p', 'vm', 'a_test.dart', 'b_test.dart']);
+ await expectLater(
+ test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('on the browser platform', () async {
+ var test =
+ await runTest(['-p', 'vm,chrome', 'a_test.dart', 'b_test.dart']);
+ await expectLater(
+ test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ }, skip: 'https://github.com/dart-lang/test/issues/1803');
+ });
+}
diff --git a/pkgs/test/test/runner/set_up_all_test.dart b/pkgs/test/test/runner/set_up_all_test.dart
new file mode 100644
index 0000000..d1259e1
--- /dev/null
+++ b/pkgs/test/test/runner/set_up_all_test.dart
@@ -0,0 +1,98 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('an error causes the run to fail', () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ setUpAll(() => throw "oh no");
+
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: (setUpAll) [E]')));
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test("doesn't run if no tests in the group are selected", () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("with setUpAll", () {
+ setUpAll(() => throw "oh no");
+
+ test("test", () {});
+ });
+
+ group("without setUpAll", () {
+ test("test", () {});
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart', '--name', 'without']);
+ expect(test.stdout, neverEmits(contains('(setUpAll)')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run if no tests in the group match the platform", () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group1", () {
+ setUpAll(() => throw "oh no");
+
+ test("with", () {}, testOn: "browser");
+ });
+
+ group("group2", () {
+ test("without", () {});
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, neverEmits(contains('(setUpAll)')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run if the group doesn't match the platform", () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group1", () {
+ setUpAll(() => throw "oh no");
+
+ test("with", () {});
+ }, testOn: "browser");
+
+ group("group2", () {
+ test("without", () {});
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, neverEmits(contains('(setUpAll)')));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/shard_test.dart b/pkgs/test/test/runner/shard_test.dart
new file mode 100644
index 0000000..8d4be86
--- /dev/null
+++ b/pkgs/test/test/runner/shard_test.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('divides all the tests among the available shards', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {});
+ test("test 2", () {});
+ test("test 3", () {});
+ test("test 4", () {});
+ test("test 5", () {});
+ test("test 6", () {});
+ test("test 7", () {});
+ test("test 8", () {});
+ test("test 9", () {});
+ test("test 10", () {});
+ }
+ ''').create();
+
+ var test =
+ await runTest(['test.dart', '--shard-index=0', '--total-shards=3']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: test 1',
+ '+1: test 2',
+ '+2: test 3',
+ '+3: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+
+ test = await runTest(['test.dart', '--shard-index=1', '--total-shards=3']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: test 4',
+ '+1: test 5',
+ '+2: test 6',
+ '+3: test 7',
+ '+4: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+
+ test = await runTest(['test.dart', '--shard-index=2', '--total-shards=3']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: test 8',
+ '+1: test 9',
+ '+2: test 10',
+ '+3: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('shards each suite', () async {
+ await d.file('1_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1.1", () {});
+ test("test 1.2", () {});
+ test("test 1.3", () {});
+ }
+ ''').create();
+
+ await d.file('2_test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 2.1", () {});
+ test("test 2.2", () {});
+ test("test 2.3", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['.', '--shard-index=0', '--total-shards=3']);
+ expect(
+ test.stdout,
+ emitsInOrder([
+ emitsAnyOf([
+ containsInOrder(
+ ['+0: ./1_test.dart: test 1.1', '+1: ./2_test.dart: test 2.1']),
+ containsInOrder(
+ ['+0: ./2_test.dart: test 2.1', '+1: ./1_test.dart: test 1.1'])
+ ]),
+ contains('+2: All tests passed!')
+ ]));
+ await test.shouldExit(0);
+
+ test = await runTest(['.', '--shard-index=1', '--total-shards=3']);
+ expect(
+ test.stdout,
+ emitsInOrder([
+ emitsAnyOf([
+ containsInOrder(
+ ['+0: ./1_test.dart: test 1.2', '+1: ./2_test.dart: test 2.2']),
+ containsInOrder(
+ ['+0: ./2_test.dart: test 2.2', '+1: ./1_test.dart: test 1.2'])
+ ]),
+ contains('+2: All tests passed!')
+ ]));
+ await test.shouldExit(0);
+
+ test = await runTest(['.', '--shard-index=2', '--total-shards=3']);
+ expect(
+ test.stdout,
+ emitsInOrder([
+ emitsAnyOf([
+ containsInOrder(
+ ['+0: ./1_test.dart: test 1.3', '+1: ./2_test.dart: test 2.3']),
+ containsInOrder(
+ ['+0: ./2_test.dart: test 2.3', '+1: ./1_test.dart: test 1.3'])
+ ]),
+ contains('+2: All tests passed!')
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('an empty shard reports success', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("test 1", () {});
+ test("test 2", () {});
+ }
+ ''').create();
+
+ var test =
+ await runTest(['test.dart', '--shard-index=1', '--total-shards=3']);
+ expect(test.stdout, emitsThrough('No tests ran.'));
+ await test.shouldExit(79);
+ });
+
+ group('reports an error if', () {
+ test('--shard-index is provided alone', () async {
+ var test = await runTest(['--shard-index=1']);
+ expect(
+ test.stderr,
+ emits(
+ '--shard-index and --total-shards may only be passed together.'));
+ await test.shouldExit(exit_codes.usage);
+ });
+
+ test('--total-shards is provided alone', () async {
+ var test = await runTest(['--total-shards=5']);
+ expect(
+ test.stderr,
+ emits(
+ '--shard-index and --total-shards may only be passed together.'));
+ await test.shouldExit(exit_codes.usage);
+ });
+
+ test('--shard-index is negative', () async {
+ var test = await runTest(['--shard-index=-1', '--total-shards=5']);
+ expect(test.stderr, emits('--shard-index may not be negative.'));
+ await test.shouldExit(exit_codes.usage);
+ });
+
+ test('--shard-index is equal to --total-shards', () async {
+ var test = await runTest(['--shard-index=5', '--total-shards=5']);
+ expect(test.stderr,
+ emits('--shard-index must be less than --total-shards.'));
+ await test.shouldExit(exit_codes.usage);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/signal_test.dart b/pkgs/test/test/runner/signal_test.dart
new file mode 100644
index 0000000..d14ee97
--- /dev/null
+++ b/pkgs/test/test/runner/signal_test.dart
@@ -0,0 +1,236 @@
+// Copyright (c) 2015, 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.
+
+// Windows doesn't support sending signals.
+@TestOn('vm && !windows')
+library;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+import 'package:test_process/test_process.dart';
+
+import '../io.dart';
+
+String get _tempDir => p.join(d.sandbox, 'tmp');
+
+// This test is inherently prone to race conditions. If it fails, it will likely
+// do so flakily, but if it succeeds, it will succeed consistently. The tests
+// represent a best effort to kill the test runner at certain times during its
+// execution.
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ setUp(() => d.dir('tmp').create());
+
+ group('during loading,', () {
+ test('cleans up if killed while loading a VM test', () async {
+ await d.file('test.dart', '''
+void main() {
+ print("in test.dart");
+ // Spin for a long time so the test is probably killed while still loading.
+ for (var i = 0; i < 100000000; i++) {}
+}
+''').create();
+
+ var test = await _runTest(['test.dart']);
+ await expectLater(test.stdout, emitsThrough('in test.dart'));
+ await signalAndQuit(test);
+
+ expectTempDirEmpty();
+ });
+
+ test('cleans up if killed while loading a browser test', () async {
+ await d.file('test.dart', 'void main() {}').create();
+
+ var test = await _runTest(['-p', 'chrome', 'test.dart']);
+ await expectLater(
+ test.stdout, emitsThrough(endsWith('loading test.dart')));
+ await signalAndQuit(test);
+
+ expectTempDirEmpty(skip: 'Failing on Travis.');
+ }, tags: 'chrome');
+
+ test('exits immediately if ^C is sent twice', () async {
+ await d.file('test.dart', '''
+void main() {
+ print("in test.dart");
+ while (true) {}
+}
+''').create();
+
+ var test = await _runTest(['test.dart']);
+ await expectLater(test.stdout, emitsThrough('in test.dart'));
+ test.signal(ProcessSignal.sigterm);
+
+ // TODO(nweiz): Sending two signals in close succession can cause the
+ // second one to be ignored, so we wait a bit before the second
+ // one. Remove this hack when issue 23047 is fixed.
+ await Future<void>.delayed(const Duration(seconds: 1));
+
+ await signalAndQuit(test);
+ });
+ });
+
+ group('during test running', () {
+ test('waits for a VM test to finish running', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+void main() {
+ tearDownAll(() {
+ File("output_all").writeAsStringSync("ran tearDownAll");
+ });
+
+ tearDown(() => File("output").writeAsStringSync("ran tearDown"));
+
+ test("test", () {
+ print("running test");
+ return Future.delayed(Duration(seconds: 1));
+ });
+}
+''').create();
+
+ var test = await _runTest(['test.dart']);
+ await expectLater(test.stdout, emitsThrough('running test'));
+ await signalAndQuit(test);
+
+ await d.file('output', 'ran tearDown').validate();
+ await d.file('output_all', 'ran tearDownAll').validate();
+ expectTempDirEmpty();
+ });
+
+ test('waits for an active tearDownAll to finish running', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+void main() {
+ tearDownAll(() async {
+ print("running tearDownAll");
+ await Future.delayed(Duration(seconds: 1));
+ File("output").writeAsStringSync("ran tearDownAll");
+ });
+
+ test("test", () {});
+}
+''').create();
+
+ var test = await _runTest(['test.dart']);
+ await expectLater(test.stdout, emitsThrough('running tearDownAll'));
+ await signalAndQuit(test);
+
+ await d.file('output', 'ran tearDownAll').validate();
+ expectTempDirEmpty();
+ });
+
+ test('kills a browser test immediately', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("test", () {
+ print("running test");
+
+ // Allow an event loop to pass so the preceding print can be handled.
+ return Future(() {
+ // Loop forever so that if the test isn't stopped while running, it never
+ // stops.
+ while (true) {}
+ });
+ });
+}
+''').create();
+
+ var test = await _runTest(['-p', 'chrome', 'test.dart']);
+ await expectLater(test.stdout, emitsThrough('running test'));
+ await signalAndQuit(test);
+
+ expectTempDirEmpty(skip: 'Failing on Travis.');
+ }, tags: 'chrome');
+
+ test('kills a VM test immediately if ^C is sent twice', () async {
+ await d.file('test.dart', '''
+import 'package:test/test.dart';
+
+void main() {
+ test("test", () {
+ print("running test");
+ while (true) {}
+ });
+}
+''').create();
+
+ var test = await _runTest(['test.dart']);
+ await expectLater(test.stdout, emitsThrough('running test'));
+ test.signal(ProcessSignal.sigterm);
+
+ // TODO(nweiz): Sending two signals in close succession can cause the
+ // second one to be ignored, so we wait a bit before the second
+ // one. Remove this hack when issue 23047 is fixed.
+ await Future<void>.delayed(const Duration(seconds: 1));
+ await signalAndQuit(test);
+ });
+
+ test('causes expectAsync() to always throw an error immediately', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+void main() {
+ var expectAsyncThrewError = false;
+
+ tearDown(() {
+ File("output").writeAsStringSync(expectAsyncThrewError.toString());
+ });
+
+ test("test", () async {
+ print("running test");
+
+ await Future.delayed(Duration(seconds: 1));
+ try {
+ expectAsync0(() {});
+ } catch (_) {
+ expectAsyncThrewError = true;
+ }
+ });
+}
+''').create();
+
+ var test = await _runTest(['test.dart']);
+ await expectLater(test.stdout, emitsThrough('running test'));
+ await signalAndQuit(test);
+
+ await d.file('output', 'true').validate();
+ expectTempDirEmpty();
+ });
+ });
+}
+
+Future<TestProcess> _runTest(List<String> args, {bool forwardStdio = false}) =>
+ runTest(args,
+ environment: {'_UNITTEST_TEMP_DIR': _tempDir},
+ forwardStdio: forwardStdio);
+
+Future<void> signalAndQuit(TestProcess test) async {
+ test.signal(ProcessSignal.sigterm);
+ await test.shouldExit();
+ await expectLater(test.stderr, emitsDone);
+}
+
+void expectTempDirEmpty({Object? skip}) {
+ expect(Directory(_tempDir).listSync(), isEmpty, skip: skip);
+}
diff --git a/pkgs/test/test/runner/skip_expect_test.dart b/pkgs/test/test/runner/skip_expect_test.dart
new file mode 100644
index 0000000..e22332c
--- /dev/null
+++ b/pkgs/test/test/runner/skip_expect_test.dart
@@ -0,0 +1,270 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ group('a skipped expect', () {
+ test('marks the test as skipped', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("skipped", () => expect(1, equals(2), skip: true));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('~1: All tests skipped.')));
+ await test.shouldExit(0);
+ });
+
+ test('prints the skip reason if there is one', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("skipped", () => expect(1, equals(2),
+ reason: "1 is 2", skip: "is failing"));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: skipped',
+ ' Skip expect: is failing',
+ '~1: All tests skipped.'
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test("prints the expect reason if there's no skip reason", () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("skipped", () => expect(1, equals(2),
+ reason: "1 is 2", skip: true));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: skipped',
+ ' Skip expect (1 is 2).',
+ '~1: All tests skipped.'
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('prints the matcher description if there are no reasons', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("skipped", () => expect(1, equals(2), skip: true));
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: skipped',
+ ' Skip expect (<2>).',
+ '~1: All tests skipped.'
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('still allows the test to fail', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failing", () {
+ expect(1, equals(2), skip: true);
+ expect(1, equals(2));
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: failing',
+ ' Skip expect (<2>).',
+ '+0 -1: failing [E]',
+ ' Expected: <2>',
+ ' Actual: <1>',
+ '+0 -1: Some tests failed.'
+ ]));
+ await test.shouldExit(1);
+ });
+ });
+
+ group('markTestSkipped', () {
+ test('prints the skip reason', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test('skipped', () {
+ markTestSkipped('some reason');
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: skipped',
+ ' some reason',
+ '~1: All tests skipped.',
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('still allows the test to fail', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test('failing', () {
+ markTestSkipped('some reason');
+ expect(1, equals(2));
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: failing',
+ ' some reason',
+ '+0 -1: failing [E]',
+ ' Expected: <2>',
+ ' Actual: <1>',
+ '+0 -1: Some tests failed.'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('error when called after the test succeeded', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ var skipCompleter = Completer();
+ var waitCompleter = Completer();
+ test('skip', () {
+ skipCompleter.future.then((_) {
+ waitCompleter.complete();
+ markTestSkipped('some reason');
+ });
+ });
+
+ // Trigger the skip completer in a following test to ensure that it
+ // only fires after skip has completed successfully.
+ test('wait', () async {
+ skipCompleter.complete();
+ await waitCompleter.future;
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: skip',
+ '+1: wait',
+ '+0 -1: skip',
+ 'This test was marked as skipped after it had already completed.',
+ 'Make sure to use a matching library which informs the test runner',
+ 'of pending async work.',
+ '+1 -1: Some tests failed.'
+ ]));
+ await test.shouldExit(1);
+ });
+ });
+
+ group('errors', () {
+ test('when called after the test succeeded', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ var skipCompleter = Completer();
+ var waitCompleter = Completer();
+ test("skip", () {
+ skipCompleter.future.then((_) {
+ waitCompleter.complete();
+ expect(1, equals(2), skip: true);
+ });
+ });
+
+ // Trigger the skip completer in a following test to ensure that it
+ // only fires after skip has completed successfully.
+ test("wait", () async {
+ skipCompleter.complete();
+ await waitCompleter.future;
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ '+0: skip',
+ '+1: wait',
+ '+0 -1: skip',
+ 'This test was marked as skipped after it had already completed.',
+ 'Make sure to use a matching library which informs the test runner',
+ 'of pending async work.',
+ '+1 -1: Some tests failed.'
+ ]));
+ await test.shouldExit(1);
+ });
+
+ test('when an invalid type is used for skip', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failing", () {
+ expect(1, equals(2), skip: 10);
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Invalid argument (skip)', '+0 -1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+ });
+}
diff --git a/pkgs/test/test/runner/solo_test.dart b/pkgs/test/test/runner/solo_test.dart
new file mode 100644
index 0000000..87f6f25
--- /dev/null
+++ b/pkgs/test/test/runner/solo_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2018, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('only runs the tests marked as solo', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("passes", () {
+ expect(true, isTrue);
+ }, solo: true);
+ test("failed", () {
+ throw 'some error';
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+1 ~1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('only runs groups marked as solo', () async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ group('solo', () {
+ test("first pass", () {
+ expect(true, isTrue);
+ });
+ test("second pass", () {
+ expect(true, isTrue);
+ });
+ }, solo: true);
+ group('no solo', () {
+ test("failure", () {
+ throw 'some error';
+ });
+ test("another failure", () {
+ throw 'some error';
+ });
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('+2 ~1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/tag_test.dart b/pkgs/test/test/runner/tag_test.dart
new file mode 100644
index 0000000..da9dca7
--- /dev/null
+++ b/pkgs/test/test/runner/tag_test.dart
@@ -0,0 +1,370 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ setUp(() async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("no tags", () {});
+ test("a", () {}, tags: "a");
+ test("b", () {}, tags: "b");
+ test("bc", () {}, tags: ["b", "c"]);
+ }
+ ''').create();
+ });
+
+ group('--tags', () {
+ test('runs all tests when no tags are specified', () async {
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, tagWarnings(['a', 'b', 'c']));
+ expect(test.stdout, emitsThrough(contains(': no tags')));
+ expect(test.stdout, emitsThrough(contains(': a')));
+ expect(test.stdout, emitsThrough(contains(': b')));
+ expect(test.stdout, emitsThrough(contains(': bc')));
+ expect(test.stdout, emitsThrough(contains('+4: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('runs a test with only a specified tag', () async {
+ var test = await runTest(['--tags=a', 'test.dart']);
+ expect(test.stdout, tagWarnings(['b', 'c']));
+ expect(test.stdout, emitsThrough(contains(': a')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('runs a test with a specified tag among others', () async {
+ var test = await runTest(['--tags=c', 'test.dart']);
+ expect(test.stdout, tagWarnings(['a', 'b']));
+ expect(test.stdout, emitsThrough(contains(': bc')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('with multiple tags, runs only tests matching all of them', () async {
+ var test = await runTest(['--tags=b,c', 'test.dart']);
+ expect(test.stdout, tagWarnings(['a']));
+ expect(test.stdout, emitsThrough(contains(': bc')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('supports boolean selector syntax', () async {
+ var test = await runTest(['--tags=b || c', 'test.dart']);
+ expect(test.stdout, tagWarnings(['a']));
+ expect(test.stdout, emitsThrough(contains(': b')));
+ expect(test.stdout, emitsThrough(contains(': bc')));
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('prints no warnings when all tags are specified', () async {
+ var test = await runTest(['--tags=a,b,c', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+ });
+
+ group('--exclude-tags', () {
+ test("dosn't run a test with only an excluded tag", () async {
+ var test = await runTest(['--exclude-tags=a', 'test.dart']);
+ expect(test.stdout, tagWarnings(['b', 'c']));
+ expect(test.stdout, emitsThrough(contains(': no tags')));
+ expect(test.stdout, emitsThrough(contains(': b')));
+ expect(test.stdout, emitsThrough(contains(': bc')));
+ expect(test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run a test with an excluded tag among others", () async {
+ var test = await runTest(['--exclude-tags=c', 'test.dart']);
+ expect(test.stdout, tagWarnings(['a', 'b']));
+ expect(test.stdout, emitsThrough(contains(': no tags')));
+ expect(test.stdout, emitsThrough(contains(': a')));
+ expect(test.stdout, emitsThrough(contains(': b')));
+ expect(test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("dosn't load a suite with an excluded tag", () async {
+ await d.file('test.dart', '''
+ @Tags(const ["a"])
+
+ import 'package:test/test.dart';
+
+ void main() {
+ throw "error";
+ }
+ ''').create();
+
+ var test = await runTest(['--exclude-tags=a', 'test.dart']);
+ expect(test.stdout, emits('No tests ran.'));
+ await test.shouldExit(79);
+ });
+
+ test('allows unused tags', () async {
+ var test = await runTest(['--exclude-tags=b,z', 'test.dart']);
+ expect(test.stdout, tagWarnings(['a', 'c']));
+ expect(test.stdout, emitsThrough(contains(': no tags')));
+ expect(test.stdout, emitsThrough(contains(': a')));
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('supports boolean selector syntax', () async {
+ var test = await runTest(['--exclude-tags=b && c', 'test.dart']);
+ expect(test.stdout, tagWarnings(['a']));
+ expect(test.stdout, emitsThrough(contains(': no tags')));
+ expect(test.stdout, emitsThrough(contains(': a')));
+ expect(test.stdout, emitsThrough(contains(': b')));
+ expect(test.stdout, emitsThrough(contains('+3: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('prints no warnings when all tags are specified', () async {
+ var test = await runTest(['--exclude-tags=a,b,c', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains(': no tags')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+ });
+
+ group('with a tagged group', () {
+ setUp(() async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("a", () {
+ test("in", () {});
+ }, tags: "a");
+
+ test("out", () {});
+ }
+ ''').create();
+ });
+
+ test('includes tags specified on the group', () async {
+ var test = await runTest(['-x', 'a', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains(': out')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test('excludes tags specified on the group', () async {
+ var test = await runTest(['-t', 'a', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains(': a in')));
+ expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
+ await test.shouldExit(0);
+ });
+ });
+
+ test('respects top-level @Tags annotations', () async {
+ await d.file('test.dart', '''
+ @Tags(const ['a'])
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['-x', 'a', 'test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran')));
+ await test.shouldExit(79);
+ });
+
+ group('warning formatting', () {
+ test('for multiple tags', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo", () {}, tags: ["a", "b"]);
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(lines(
+ 'Warning: Tags were used that weren\'t specified in dart_test.yaml.\n'
+ ' a was used in the test "foo"\n'
+ ' b was used in the test "foo"')));
+ await test.shouldExit(0);
+ });
+
+ test('for multiple tests', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo", () {}, tags: "a");
+ test("bar", () {}, tags: "a");
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(lines(
+ 'Warning: A tag was used that wasn\'t specified in dart_test.yaml.\n'
+ ' a was used in:\n'
+ ' the test "foo"\n'
+ ' the test "bar"')));
+ await test.shouldExit(0);
+ });
+
+ test('for groups', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ test("foo", () {});
+ test("bar", () {});
+ }, tags: "a");
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(lines(
+ 'Warning: A tag was used that wasn\'t specified in dart_test.yaml.\n'
+ ' a was used in the group "group"')));
+ await test.shouldExit(0);
+ });
+
+ test('for suites', () async {
+ await d.file('test.dart', '''
+ @Tags(const ["a"])
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo", () {});
+ test("bar", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(lines(
+ 'Warning: A tag was used that wasn\'t specified in dart_test.yaml.\n'
+ ' a was used in the suite itself')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't double-print a tag warning", () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo", () {}, tags: "a");
+ }
+ ''').create();
+
+ var test = await runTest(['-p', 'vm,chrome', 'test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(lines(
+ 'Warning: A tag was used that wasn\'t specified in dart_test.yaml.\n'
+ ' a was used in the test "foo"')));
+ expect(test.stdout, neverEmits(startsWith('Warning:')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+ });
+
+ group('invalid tags', () {
+ test('are disallowed by test()', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo", () {}, tags: "a b");
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ ' Failed to load "test.dart": Invalid argument(s): Invalid tag "a '
+ 'b". Tags must be (optionally hyphenated) Dart identifiers.'));
+ await test.shouldExit(1);
+ });
+
+ test('are disallowed by group()', () async {
+ await d.file('test.dart', '''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ test("foo", () {});
+ }, tags: "a b");
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(
+ ' Failed to load "test.dart": Invalid argument(s): Invalid tag "a '
+ 'b". Tags must be (optionally hyphenated) Dart identifiers.'));
+ await test.shouldExit(1);
+ });
+
+ test('are disallowed by @Tags()', () async {
+ await d.file('test.dart', '''
+ @Tags(const ["a b"])
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("foo", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ emitsThrough(lines(' Failed to load "test.dart":\n'
+ ' Error on line 1, column 22: Invalid tag name. Tags must be '
+ '(optionally hyphenated) Dart identifiers.')));
+ await test.shouldExit(1);
+ });
+ });
+}
+
+/// Returns a [StreamMatcher] that asserts that a test emits warnings for [tags]
+/// in order.
+StreamMatcher tagWarnings(List<String> tags) => emitsInOrder([
+ emitsThrough(
+ "Warning: ${tags.length == 1 ? 'A tag was' : 'Tags were'} used that "
+ "${tags.length == 1 ? "wasn't" : "weren't"} specified in "
+ 'dart_test.yaml.'),
+
+ for (var tag in tags) emitsThrough(startsWith(' $tag was used in')),
+
+ // Consume until the end of the warning block, and assert that it has no
+ // further tags than the ones we specified.
+ mayEmitMultiple(isNot(anyOf([contains(' was used in'), isEmpty]))),
+ isEmpty,
+ ]);
+
+/// Returns a [StreamMatcher] that matches the lines of [string] in order.
+StreamMatcher lines(String string) => emitsInOrder(string.split('\n'));
diff --git a/pkgs/test/test/runner/tear_down_all_test.dart b/pkgs/test/test/runner/tear_down_all_test.dart
new file mode 100644
index 0000000..93ede4f
--- /dev/null
+++ b/pkgs/test/test/runner/tear_down_all_test.dart
@@ -0,0 +1,98 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('an error causes the run to fail', () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ tearDownAll(() => throw "oh no");
+
+ test("test", () {});
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, emitsThrough(contains('-1: (tearDownAll) [E]')));
+ expect(test.stdout, emitsThrough(contains('-1: Some tests failed.')));
+ await test.shouldExit(1);
+ });
+
+ test("doesn't run if no tests in the group are selected", () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("with tearDownAll", () {
+ tearDownAll(() => throw "oh no");
+
+ test("test", () {});
+ });
+
+ group("without tearDownAll", () {
+ test("test", () {});
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart', '--name', 'without']);
+ expect(test.stdout, neverEmits(contains('(tearDownAll)')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run if no tests in the group match the platform", () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group1", () {
+ tearDownAll(() => throw "oh no");
+
+ test("with", () {}, testOn: "browser");
+ });
+
+ group("group2", () {
+ test("without", () {});
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, neverEmits(contains('(tearDownAll)')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run if the group doesn't match the platform", () async {
+ await d.file('test.dart', r'''
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group1", () {
+ tearDownAll(() => throw "oh no");
+
+ test("with", () {});
+ }, testOn: "browser");
+
+ group("group2", () {
+ test("without", () {});
+ });
+ }
+ ''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(test.stdout, neverEmits(contains('(tearDownAll)')));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/runner/test_chain_test.dart b/pkgs/test/test/runner/test_chain_test.dart
new file mode 100644
index 0000000..ad814de
--- /dev/null
+++ b/pkgs/test/test/runner/test_chain_test.dart
@@ -0,0 +1,84 @@
+// 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')
+library;
+
+import 'dart:convert';
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ setUp(() async {
+ await d.file('test.dart', '''
+ import 'dart:async';
+
+ import 'package:test/test.dart';
+
+ void main() {
+ test("failure", () async{
+ await Future((){});
+ await Future((){});
+ throw "oh no";
+ });
+ }
+ ''').create();
+ });
+ test('folds packages contained in the except list', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'fold_stack_frames': {
+ 'except': ['stream_channel']
+ }
+ }))
+ .create();
+ var test = await runTest(['test.dart']);
+ expect(test.stdoutStream(), neverEmits(contains('package:stream_channel')));
+ await test.shouldExit(1);
+ });
+
+ test('by default folds both stream_channel and test packages', () async {
+ var test = await runTest(['test.dart']);
+ expect(test.stdoutStream(), neverEmits(contains('package:test')));
+ expect(test.stdoutStream(), neverEmits(contains('package:stream_channel')));
+ await test.shouldExit(1);
+ });
+
+ test('folds all packages not contained in the only list', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'fold_stack_frames': {
+ 'only': ['test']
+ }
+ }))
+ .create();
+ var test = await runTest(['test.dart']);
+ expect(test.stdoutStream(), neverEmits(contains('package:stream_channel')));
+ await test.shouldExit(1);
+ });
+
+ test('does not fold packages in the only list', () async {
+ await d
+ .file(
+ 'dart_test.yaml',
+ jsonEncode({
+ 'fold_stack_frames': {
+ 'only': ['test_api']
+ }
+ }))
+ .create();
+ var test = await runTest(['test.dart']);
+ expect(test.stdoutStream(), emitsThrough(contains('package:test_api')));
+ await test.shouldExit(1);
+ });
+}
diff --git a/pkgs/test/test/runner/test_on_test.dart b/pkgs/test/test/runner/test_on_test.dart
new file mode 100644
index 0000000..96baf0b
--- /dev/null
+++ b/pkgs/test/test/runner/test_on_test.dart
@@ -0,0 +1,226 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:isolate';
+
+import 'package:package_config/package_config.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';
+
+void main() {
+ late PackageConfig currentPackageConfig;
+
+ setUpAll(() async {
+ await precompileTestExecutable();
+ currentPackageConfig =
+ await loadPackageConfigUri((await Isolate.packageConfig)!);
+ });
+
+ setUp(() async {
+ await d
+ .file('package_config.json',
+ jsonEncode(PackageConfig.toJson(currentPackageConfig)))
+ .create();
+ });
+
+ group('for suite', () {
+ test('runs a test suite on a matching platform', () async {
+ await _writeTestFile('vm_test.dart', suiteTestOn: 'vm');
+
+ var test = await runTest(['vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run a test suite on a non-matching platform", () async {
+ await _writeTestFile('vm_test.dart', suiteTestOn: 'vm');
+
+ var test = await runTest(['--platform', 'chrome', 'vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ }, tags: 'chrome');
+
+ test('runs a test suite on a matching operating system', () async {
+ await _writeTestFile('os_test.dart', suiteTestOn: currentOS.identifier);
+
+ var test = await runTest(['os_test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run a test suite on a non-matching operating system",
+ () async {
+ await _writeTestFile('os_test.dart',
+ suiteTestOn: otherOS, loadable: false);
+
+ var test = await runTest(['os_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('only loads matching files when loading as a group', () async {
+ await _writeTestFile('vm_test.dart', suiteTestOn: 'vm');
+ await _writeTestFile('browser_test.dart',
+ suiteTestOn: 'browser', loadable: false);
+ await _writeTestFile('this_os_test.dart',
+ suiteTestOn: currentOS.identifier);
+ await _writeTestFile('other_os_test.dart',
+ suiteTestOn: otherOS, loadable: false);
+
+ var test = await runTest(['.']);
+ expect(test.stdout, emitsThrough(contains('+2: All tests passed!')));
+ await test.shouldExit(0);
+ });
+ });
+
+ group('for group', () {
+ test('runs a VM group on the VM', () async {
+ await _writeTestFile('vm_test.dart', groupTestOn: 'vm');
+
+ var test = await runTest(['vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run a Browser group on the VM", () async {
+ await _writeTestFile('browser_test.dart', groupTestOn: 'browser');
+
+ var test = await runTest(['browser_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('runs a browser group on a browser', () async {
+ await _writeTestFile('browser_test.dart', groupTestOn: 'browser');
+
+ var test = await runTest(['--platform', 'chrome', 'browser_test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test("doesn't run a VM group on a browser", () async {
+ await _writeTestFile('vm_test.dart', groupTestOn: 'vm');
+
+ var test = await runTest(['--platform', 'chrome', 'vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ }, tags: 'chrome');
+ });
+
+ group('for test', () {
+ test('runs a VM test on the VM', () async {
+ await _writeTestFile('vm_test.dart', testTestOn: 'vm');
+
+ var test = await runTest(['vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't run a browser test on the VM", () async {
+ await _writeTestFile('browser_test.dart', testTestOn: 'browser');
+
+ var test = await runTest(['browser_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test('runs a browser test on a browser', () async {
+ await _writeTestFile('browser_test.dart', testTestOn: 'browser');
+
+ var test = await runTest(['--platform', 'chrome', 'browser_test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ }, tags: 'chrome');
+
+ test("doesn't run a VM test on a browser", () async {
+ await _writeTestFile('vm_test.dart', testTestOn: 'vm');
+
+ var test = await runTest(['--platform', 'chrome', 'vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ }, tags: 'chrome');
+ });
+
+ group('with suite, group, and test selectors', () {
+ test('runs the test if all selectors match', () async {
+ await _writeTestFile('vm_test.dart',
+ suiteTestOn: '!browser', groupTestOn: '!js', testTestOn: 'vm');
+
+ var test = await runTest(['vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('All tests passed!')));
+ await test.shouldExit(0);
+ });
+
+ test("doesn't runs the test if the suite doesn't match", () async {
+ await _writeTestFile('vm_test.dart',
+ suiteTestOn: 'browser', groupTestOn: '!js', testTestOn: 'vm');
+
+ var test = await runTest(['vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test("doesn't runs the test if the group doesn't match", () async {
+ await _writeTestFile('vm_test.dart',
+ suiteTestOn: '!browser', groupTestOn: 'browser', testTestOn: 'vm');
+
+ var test = await runTest(['vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+
+ test("doesn't runs the test if the test doesn't match", () async {
+ await _writeTestFile('vm_test.dart',
+ suiteTestOn: '!browser', groupTestOn: '!js', testTestOn: 'browser');
+
+ var test = await runTest(['vm_test.dart']);
+ expect(test.stdout, emitsThrough(contains('No tests ran.')));
+ await test.shouldExit(79);
+ });
+ });
+}
+
+/// Writes a test file with some platform selectors to [filename].
+///
+/// Each of [suiteTestOn], [groupTestOn], and [testTestOn] is a platform
+/// selector that's suite-, group-, and test-level respectively. If [loadable]
+/// is `false`, the test file will be made unloadable on the Dart VM.
+Future<void> _writeTestFile(String filename,
+ {String? suiteTestOn,
+ String? groupTestOn,
+ String? testTestOn,
+ bool loadable = true}) {
+ var buffer = StringBuffer();
+ if (suiteTestOn != null) buffer.writeln("@TestOn('$suiteTestOn')");
+ if (!loadable) buffer.writeln("import 'dart:js_util';");
+
+ buffer
+ ..writeln("import 'package:test/test.dart';")
+ ..writeln('void main() {')
+ ..writeln(" group('group', () {");
+
+ if (testTestOn != null) {
+ buffer.writeln(" test('test', () {}, testOn: '$testTestOn');");
+ } else {
+ buffer.writeln(" test('test', () {});");
+ }
+
+ if (groupTestOn != null) {
+ buffer.writeln(" }, testOn: '$groupTestOn');");
+ } else {
+ buffer.writeln(' });');
+ }
+
+ buffer.writeln('}');
+
+ return d.file(filename, buffer.toString()).create();
+}
diff --git a/pkgs/test/test/runner/timeout_test.dart b/pkgs/test/test/runner/timeout_test.dart
new file mode 100644
index 0000000..6f4b0e7
--- /dev/null
+++ b/pkgs/test/test/runner/timeout_test.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2016, 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')
+library;
+
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+import '../io.dart';
+
+void main() {
+ setUpAll(precompileTestExecutable);
+
+ test('respects top-level @Timeout declarations', () async {
+ await d.file('test.dart', '''
+@Timeout(const Duration(seconds: 0))
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("timeout", () async {
+ await Future.delayed(Duration.zero);
+ });
+}
+''').create();
+
+ var test = await runTest(['test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('respects the --timeout flag', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("timeout", () async {
+ await Future.delayed(Duration.zero);
+ });
+}
+''').create();
+
+ var test = await runTest(['--timeout=0s', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('timeout is reset with each retry', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ var runCount = 0;
+ test("timeout", () async {
+ runCount++;
+ if (runCount <=2) {
+ await Future.delayed(Duration(milliseconds: 1000));
+ }
+ }, retry: 3);
+}
+''').create();
+
+ var test = await runTest(['--timeout=400ms', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder([
+ 'Test timed out after 0.4 seconds.',
+ 'Test timed out after 0.4 seconds.',
+ '+1: All tests passed!'
+ ]));
+ await test.shouldExit(0);
+ });
+
+ test('the --timeout flag applies on top of the default 30s timeout',
+ () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("no timeout", () async {
+ await Future.delayed(Duration(milliseconds: 250));
+ });
+
+ test("timeout", () async {
+ await Future.delayed(Duration(milliseconds: 750));
+ });
+}
+''').create();
+
+ // This should make the timeout about 500ms, which should cause exactly one
+ // test to fail.
+ var test = await runTest(['--timeout=0.016x', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0.4 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('times out teardown callbacks', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ tearDown(() async {
+ await Completer<void>().future;
+ });
+
+ test('timeout in teardown', () async {
+ // nothing
+ });
+}
+''').create();
+
+ var test = await runTest(['--timeout=50ms', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('times out after failing test', () async {
+ await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ tearDown(() async {
+ await Completer<void>().future;
+ });
+
+ test('timeout in teardown', () async {
+ expect(true, false);
+ });
+}
+''').create();
+
+ var test = await runTest(['--timeout=50ms', 'test.dart']);
+ expect(
+ test.stdout,
+ containsInOrder(
+ ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+ await test.shouldExit(1);
+ });
+
+ test('are ignored with --ignore-timeouts', () async {
+ await d.file('test.dart', '''
+@Timeout(const Duration(seconds: 0))
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+ test("timeout", () async {
+ await Future.delayed(Duration(milliseconds: 10));
+ });
+}
+''').create();
+
+ var test = await runTest(['test.dart', '--ignore-timeouts']);
+ expect(test.stdout, containsInOrder(['+1: All tests passed!']));
+ await test.shouldExit(0);
+ });
+}
diff --git a/pkgs/test/test/util/one_off_handler_test.dart b/pkgs/test/test/util/one_off_handler_test.dart
new file mode 100644
index 0000000..c10a7af
--- /dev/null
+++ b/pkgs/test/test/util/one_off_handler_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2015, 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 'package:shelf/shelf.dart' as shelf;
+import 'package:test/src/util/one_off_handler.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late OneOffHandler handler;
+ setUp(() => handler = OneOffHandler());
+
+ Future<shelf.Response> handle(shelf.Request request) =>
+ Future.sync(() => handler.handler(request));
+
+ test('returns a 404 for a root URL', () async {
+ var request = shelf.Request('GET', Uri.parse('http://localhost/'));
+ expect((await handle(request)).statusCode, equals(404));
+ });
+
+ test('returns a 404 for an unhandled URL', () async {
+ var request = shelf.Request('GET', Uri.parse('http://localhost/1'));
+ expect((await handle(request)).statusCode, equals(404));
+ });
+
+ test('passes a request to a handler only once', () async {
+ var path = handler.create(expectAsync1((request) {
+ expect(request.method, equals('GET'));
+ return shelf.Response.ok('good job!');
+ }));
+
+ var request = shelf.Request('GET', Uri.parse('http://localhost/$path'));
+ var response = await handle(request);
+ expect(response.statusCode, equals(200));
+ expect(response.readAsString(), completion(equals('good job!')));
+
+ request = shelf.Request('GET', Uri.parse('http://localhost/$path'));
+ expect((await handle(request)).statusCode, equals(404));
+ });
+
+ test('passes requests to the correct handlers', () async {
+ var path1 = handler.create(expectAsync1((request) {
+ expect(request.method, equals('GET'));
+ return shelf.Response.ok('one');
+ }));
+
+ var path2 = handler.create(expectAsync1((request) {
+ expect(request.method, equals('GET'));
+ return shelf.Response.ok('two');
+ }));
+
+ var path3 = handler.create(expectAsync1((request) {
+ expect(request.method, equals('GET'));
+ return shelf.Response.ok('three');
+ }));
+
+ var request = shelf.Request('GET', Uri.parse('http://localhost/$path2'));
+ var response = await handle(request);
+ expect(response.statusCode, equals(200));
+ expect(response.readAsString(), completion(equals('two')));
+
+ request = shelf.Request('GET', Uri.parse('http://localhost/$path1'));
+ response = await handle(request);
+ expect(response.statusCode, equals(200));
+ expect(response.readAsString(), completion(equals('one')));
+
+ request = shelf.Request('GET', Uri.parse('http://localhost/$path3'));
+ response = await handle(request);
+ expect(response.statusCode, equals(200));
+ expect(response.readAsString(), completion(equals('three')));
+ });
+}
diff --git a/pkgs/test/test/util/path_handler_test.dart b/pkgs/test/test/util/path_handler_test.dart
new file mode 100644
index 0000000..9a5d98b
--- /dev/null
+++ b/pkgs/test/test/util/path_handler_test.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2015, 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 'package:shelf/shelf.dart' as shelf;
+import 'package:test/src/util/path_handler.dart';
+import 'package:test/test.dart';
+
+void main() {
+ late PathHandler handler;
+ setUp(() => handler = PathHandler());
+
+ Future<shelf.Response> localHandler(shelf.Request request) =>
+ Future.sync(() => handler.handler(request));
+
+ test('returns a 404 for a root URL', () async {
+ var request = shelf.Request('GET', Uri.parse('http://localhost/'));
+ expect((await localHandler(request)).statusCode, equals(404));
+ });
+
+ test('returns a 404 for an unregistered URL', () async {
+ var request = shelf.Request('GET', Uri.parse('http://localhost/foo'));
+ expect((await localHandler(request)).statusCode, equals(404));
+ });
+
+ test('runs a handler for an exact URL', () async {
+ var request = shelf.Request('GET', Uri.parse('http://localhost/foo'));
+ handler.add('foo', expectAsync1((request) {
+ expect(request.handlerPath, equals('/foo'));
+ expect(request.url.path, isEmpty);
+ return shelf.Response.ok('good job!');
+ }));
+
+ var response = await localHandler(request);
+ expect(response.statusCode, equals(200));
+ expect(response.readAsString(), completion(equals('good job!')));
+ });
+
+ test('runs a handler for a suffix', () async {
+ var request = shelf.Request('GET', Uri.parse('http://localhost/foo/bar'));
+ handler.add('foo', expectAsync1((request) {
+ expect(request.handlerPath, equals('/foo/'));
+ expect(request.url.path, 'bar');
+ return shelf.Response.ok('good job!');
+ }));
+
+ var response = await localHandler(request);
+ expect(response.statusCode, equals(200));
+ expect(response.readAsString(), completion(equals('good job!')));
+ });
+
+ test('runs the longest matching handler', () async {
+ var request =
+ shelf.Request('GET', Uri.parse('http://localhost/foo/bar/baz'));
+
+ handler.add(
+ 'foo',
+ expectAsync1((_) {
+ return shelf.Response.notFound('fake');
+ }, count: 0));
+ handler.add('foo/bar', expectAsync1((request) {
+ expect(request.handlerPath, equals('/foo/bar/'));
+ expect(request.url.path, 'baz');
+ return shelf.Response.ok('good job!');
+ }));
+ handler.add(
+ 'foo/bar/baz/bang',
+ expectAsync1((_) {
+ return shelf.Response.notFound('fake');
+ }, count: 0));
+
+ var response = await localHandler(request);
+ expect(response.statusCode, equals(200));
+ expect(response.readAsString(), completion(equals('good job!')));
+ });
+}
diff --git a/pkgs/test/test/util/string_literal_iterator_test.dart b/pkgs/test/test/util/string_literal_iterator_test.dart
new file mode 100644
index 0000000..03c8e34
--- /dev/null
+++ b/pkgs/test/test/util/string_literal_iterator_test.dart
@@ -0,0 +1,250 @@
+// Copyright (c) 2015, 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')
+library;
+
+import 'package:analyzer/dart/analysis/utilities.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:test/test.dart';
+import 'package:test_core/src/util/string_literal_iterator.dart';
+
+final _offset = 'final str = '.length;
+
+void main() {
+ group('returns simple characters in', () {
+ test('a single simple string', () {
+ var iter = _parse('"abc"');
+
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('a'));
+ expect(iter.offset, equals(_offset + 1));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('b'));
+ expect(iter.offset, equals(_offset + 2));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('c'));
+ expect(iter.offset, equals(_offset + 3));
+
+ expect(iter.moveNext(), isFalse);
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 4));
+ });
+
+ test('a raw string', () {
+ var iter = _parse('r"abc"');
+
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 1));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('a'));
+ expect(iter.offset, equals(_offset + 2));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('b'));
+ expect(iter.offset, equals(_offset + 3));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('c'));
+ expect(iter.offset, equals(_offset + 4));
+
+ expect(iter.moveNext(), isFalse);
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 5));
+ });
+
+ test('a multiline string', () {
+ var iter = _parse('"""ab\ncd"""');
+
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 2));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('a'));
+ expect(iter.offset, equals(_offset + 3));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('b'));
+ expect(iter.offset, equals(_offset + 4));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('\n'));
+ expect(iter.offset, equals(_offset + 5));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('c'));
+ expect(iter.offset, equals(_offset + 6));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('d'));
+ expect(iter.offset, equals(_offset + 7));
+
+ expect(iter.moveNext(), isFalse);
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 8));
+ });
+
+ test('a raw multiline string', () {
+ var iter = _parse('r"""ab\ncd"""');
+
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 3));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('a'));
+ expect(iter.offset, equals(_offset + 4));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('b'));
+ expect(iter.offset, equals(_offset + 5));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('\n'));
+ expect(iter.offset, equals(_offset + 6));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('c'));
+ expect(iter.offset, equals(_offset + 7));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('d'));
+ expect(iter.offset, equals(_offset + 8));
+
+ expect(iter.moveNext(), isFalse);
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 9));
+ });
+
+ test('adjacent strings', () {
+ var iter = _parse('"ab" r"cd" """ef\ngh"""');
+
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('a'));
+ expect(iter.offset, equals(_offset + 1));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('b'));
+ expect(iter.offset, equals(_offset + 2));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('c'));
+ expect(iter.offset, equals(_offset + 7));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('d'));
+ expect(iter.offset, equals(_offset + 8));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('e'));
+ expect(iter.offset, equals(_offset + 14));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('f'));
+ expect(iter.offset, equals(_offset + 15));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('\n'));
+ expect(iter.offset, equals(_offset + 16));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('g'));
+ expect(iter.offset, equals(_offset + 17));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('h'));
+ expect(iter.offset, equals(_offset + 18));
+
+ expect(iter.moveNext(), isFalse);
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + 19));
+ });
+ });
+
+ group('parses an escape sequence for', () {
+ test('a newline', () => _expectEscape(r'\n', '\n'));
+ test('a carriage return', () => _expectEscape(r'\r', '\r'));
+ test('a form feed', () => _expectEscape(r'\f', '\f'));
+ test('a backspace', () => _expectEscape(r'\b', '\b'));
+ test('a tab', () => _expectEscape(r'\t', '\t'));
+ test('a vertical tab', () => _expectEscape(r'\v', '\v'));
+ test('a quote', () => _expectEscape(r'\"', '"'));
+ test('a backslash', () => _expectEscape(r'\\', '\\'));
+
+ test('a hex character', () {
+ _expectEscape(r'\x62', 'b');
+ _expectEscape(r'\x7A', 'z');
+ _expectEscape(r'\x7a', 'z');
+ });
+
+ test('a fixed-length unicode character',
+ () => _expectEscape(r'\u0062', 'b'));
+
+ test('a short variable-length unicode character',
+ () => _expectEscape(r'\u{62}', 'b'));
+
+ test('a long variable-length unicode character',
+ () => _expectEscape(r'\u{000062}', 'b'));
+ });
+
+ group('throws an ArgumentError for', () {
+ test('interpolation', () {
+ expect(() => _parse(r'"$foo"'), throwsArgumentError);
+ });
+
+ test('interpolation in an adjacent string', () {
+ expect(() => _parse(r'"foo" "$bar" "baz"'), throwsArgumentError);
+ });
+ });
+}
+
+/// Asserts that [escape] is parsed as [value].
+void _expectEscape(String escape, String value) {
+ var iter = _parse('"a${escape}b"');
+
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('a'));
+ expect(iter.offset, equals(_offset + 1));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune(value));
+ expect(iter.offset, equals(_offset + 2));
+
+ expect(iter.moveNext(), isTrue);
+ expect(iter.current, _isRune('b'));
+ expect(iter.offset, equals(_offset + escape.length + 2));
+
+ expect(iter.moveNext(), isFalse);
+ expect(() => iter.current, throwsA(isA<TypeError>()));
+ expect(iter.offset, equals(_offset + escape.length + 3));
+}
+
+/// Returns a matcher that asserts that the given rune is the rune for [char].
+Matcher _isRune(String char) {
+ return predicate((rune) {
+ return rune is int && String.fromCharCode(rune) == char;
+ }, 'is the rune "$char"');
+}
+
+/// Parses [dart], which should be a string literal, into a
+/// [StringLiteralIterator].
+StringLiteralIterator _parse(String dart) {
+ var declaration = parseString(content: 'final str = $dart;')
+ .unit
+ .declarations
+ .single as TopLevelVariableDeclaration;
+ var literal = declaration.variables.variables.single.initializer;
+ return StringLiteralIterator(literal as StringLiteral);
+}
diff --git a/pkgs/test/test/utils.dart b/pkgs/test/test/utils.dart
new file mode 100644
index 0000000..23d5b7e
--- /dev/null
+++ b/pkgs/test/test/utils.dart
@@ -0,0 +1,287 @@
+// Copyright (c) 2015, 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:collection';
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:glob/glob.dart';
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/declarer.dart';
+import 'package:test_api/src/backend/group.dart';
+import 'package:test_api/src/backend/group_entry.dart';
+import 'package:test_api/src/backend/live_test.dart';
+import 'package:test_api/src/backend/platform_selector.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_api/src/backend/suite_platform.dart';
+import 'package:test_core/src/runner/application_exception.dart';
+import 'package:test_core/src/runner/compiler_selection.dart';
+import 'package:test_core/src/runner/configuration.dart';
+import 'package:test_core/src/runner/configuration/custom_runtime.dart';
+import 'package:test_core/src/runner/configuration/runtime_settings.dart';
+import 'package:test_core/src/runner/engine.dart';
+import 'package:test_core/src/runner/load_suite.dart';
+import 'package:test_core/src/runner/plugin/environment.dart';
+import 'package:test_core/src/runner/runner_suite.dart';
+import 'package:test_core/src/runner/runtime_selection.dart';
+import 'package:test_core/src/runner/suite.dart';
+
+/// A dummy suite platform to use for testing suites.
+final suitePlatform = SuitePlatform(Runtime.vm, compiler: null);
+
+// The last state change detected via [expectStates].
+State? _lastState;
+
+/// Asserts that exactly [states] will be emitted via [liveTest.onStateChange].
+///
+/// The most recent emitted state is stored in [_lastState].
+void expectStates(LiveTest liveTest, Iterable<State> statesIter) {
+ var states = Queue.of(statesIter);
+ liveTest.onStateChange.listen(expectAsync1((state) {
+ _lastState = state;
+ expect(state, equals(states.removeFirst()));
+ }, count: states.length, max: states.length));
+}
+
+/// Asserts that errors will be emitted via [liveTest.onError] that match
+/// [validators], in order.
+void expectErrors(
+ LiveTest liveTest, Iterable<void Function(Object)> validatorsIter) {
+ var validators = Queue.of(validatorsIter);
+ liveTest.onError.listen(expectAsync1((error) {
+ validators.removeFirst()(error.error);
+ }, count: validators.length, max: validators.length));
+}
+
+/// Asserts that [liveTest] will have a single failure with message `"oh no"`.
+void expectSingleFailure(LiveTest liveTest) {
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.failure)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(_lastState!.status, equals(Status.complete));
+ expect(error, _isTestFailure('oh no'));
+ }
+ ]);
+}
+
+/// Returns a matcher that matches a [TestFailure] with the given [message].
+///
+/// [message] can be a string or a [Matcher].
+Matcher _isTestFailure(Object message) => const TypeMatcher<TestFailure>()
+ .having((e) => e.message, 'message', message);
+
+/// Returns a matcher that matches a [ApplicationException] with the given
+/// [message].
+///
+/// [message] can be a string or a [Matcher].
+Matcher isApplicationException(Object message) =>
+ const TypeMatcher<ApplicationException>()
+ .having((e) => e.message, 'message', message);
+
+/// Asserts that [liveTest] has completed and passed.
+///
+/// If the test had any errors, they're surfaced nicely into the outer test.
+void expectTestPassed(LiveTest liveTest) {
+ // Since the test is expected to pass, we forward any current or future errors
+ // to the outer test, because they're definitely unexpected.
+ for (var error in liveTest.errors) {
+ registerException(error.error, error.stackTrace);
+ }
+ liveTest.onError.listen((error) {
+ registerException(error.error, error.stackTrace);
+ });
+
+ expect(liveTest.state.status, equals(Status.complete));
+ expect(liveTest.state.result, equals(Result.success));
+}
+
+/// Asserts that [liveTest] failed with a single [TestFailure] whose message
+/// matches [message].
+void expectTestFailed(LiveTest liveTest, Object message) {
+ expect(liveTest.state.status, equals(Status.complete));
+ expect(liveTest.state.result, equals(Result.failure));
+ expect(liveTest.errors, hasLength(1));
+ expect(liveTest.errors.first.error, _isTestFailure(message));
+}
+
+/// Runs [body] with a declarer and returns the declared entries.
+List<GroupEntry> declare(void Function() body) {
+ var declarer = Declarer()..declare(body);
+ return declarer.build().entries;
+}
+
+/// Runs [body] with a declarer and returns an engine that runs those tests.
+Engine declareEngine(
+ void Function() body, {
+ bool runSkipped = false,
+ String? coverage,
+ bool stopOnFirstFailure = false,
+}) {
+ var declarer = Declarer()..declare(body);
+ return Engine.withSuites(
+ [
+ RunnerSuite(
+ const PluginEnvironment(),
+ SuiteConfiguration.runSkipped(runSkipped),
+ declarer.build(),
+ suitePlatform)
+ ],
+ coverage: coverage,
+ stopOnFirstFailure: stopOnFirstFailure,
+ );
+}
+
+/// Returns a [RunnerSuite] with a default environment and configuration.
+RunnerSuite runnerSuite(Group root) => RunnerSuite(
+ const PluginEnvironment(), SuiteConfiguration.empty, root, suitePlatform);
+
+/// Returns a [LoadSuite] with a default configuration.
+LoadSuite loadSuite(String name, FutureOr<RunnerSuite> Function() body) =>
+ LoadSuite(name, SuiteConfiguration.empty, suitePlatform, body);
+
+SuiteConfiguration suiteConfiguration(
+ {bool? allowDuplicateTestNames,
+ bool? allowTestRandomization,
+ bool? jsTrace,
+ bool? runSkipped,
+ Iterable<String>? dart2jsArgs,
+ String? precompiledPath,
+ Iterable<CompilerSelection>? compilerSelections,
+ Iterable<RuntimeSelection>? runtimes,
+ Map<BooleanSelector, SuiteConfiguration>? tags,
+ Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ bool? ignoreTimeouts,
+
+ // Test-level configuration
+ Timeout? timeout,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ bool? skip,
+ int? retry,
+ String? skipReason,
+ PlatformSelector? testOn,
+ Iterable<String>? addTags}) =>
+ SuiteConfiguration(
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ allowTestRandomization: allowTestRandomization,
+ jsTrace: jsTrace,
+ runSkipped: runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ precompiledPath: precompiledPath,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ tags: tags,
+ onPlatform: onPlatform,
+ ignoreTimeouts: ignoreTimeouts,
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags);
+
+Configuration configuration(
+ {bool? help,
+ String? customHtmlTemplatePath,
+ bool? version,
+ bool? pauseAfterLoad,
+ bool? debug,
+ bool? color,
+ String? configurationPath,
+ String? reporter,
+ Map<String, String>? fileReporters,
+ String? coverage,
+ int? concurrency,
+ int? shardIndex,
+ int? totalShards,
+ Map<String, Set<TestSelection>>? testSelections,
+ Iterable<String>? foldTraceExcept,
+ Iterable<String>? foldTraceOnly,
+ Glob? filename,
+ Iterable<String>? chosenPresets,
+ Map<String, Configuration>? presets,
+ Map<String, RuntimeSettings>? overrideRuntimes,
+ Map<String, CustomRuntime>? defineRuntimes,
+ bool? noRetry,
+ bool? ignoreTimeouts,
+
+ // Suite-level configuration
+ bool? allowDuplicateTestNames,
+ bool? allowTestRandomization,
+ bool? jsTrace,
+ bool? runSkipped,
+ Iterable<String>? dart2jsArgs,
+ String? precompiledPath,
+ Iterable<Pattern>? globalPatterns,
+ Iterable<CompilerSelection>? compilerSelections,
+ Iterable<RuntimeSelection>? runtimes,
+ BooleanSelector? includeTags,
+ BooleanSelector? excludeTags,
+ Map<BooleanSelector, SuiteConfiguration>? tags,
+ Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ int? testRandomizeOrderingSeed,
+
+ // Test-level configuration
+ Timeout? timeout,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ bool? skip,
+ int? retry,
+ String? skipReason,
+ PlatformSelector? testOn,
+ Iterable<String>? addTags}) =>
+ Configuration(
+ help: help,
+ customHtmlTemplatePath: customHtmlTemplatePath,
+ version: version,
+ pauseAfterLoad: pauseAfterLoad,
+ debug: debug,
+ color: color,
+ configurationPath: configurationPath,
+ reporter: reporter,
+ fileReporters: fileReporters,
+ coverage: coverage,
+ concurrency: concurrency,
+ shardIndex: shardIndex,
+ totalShards: totalShards,
+ testSelections: testSelections,
+ foldTraceExcept: foldTraceExcept,
+ foldTraceOnly: foldTraceOnly,
+ filename: filename,
+ chosenPresets: chosenPresets,
+ presets: presets,
+ overrideRuntimes: overrideRuntimes,
+ defineRuntimes: defineRuntimes,
+ noRetry: noRetry,
+ ignoreTimeouts: ignoreTimeouts,
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ allowTestRandomization: allowTestRandomization,
+ jsTrace: jsTrace,
+ runSkipped: runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ precompiledPath: precompiledPath,
+ globalPatterns: globalPatterns,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ includeTags: includeTags,
+ excludeTags: excludeTags,
+ tags: tags,
+ onPlatform: onPlatform,
+ testRandomizeOrderingSeed: testRandomizeOrderingSeed,
+ stopOnFirstFailure: false,
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags);
diff --git a/pkgs/test/tool/README.md b/pkgs/test/tool/README.md
new file mode 100644
index 0000000..bd9ee8b
--- /dev/null
+++ b/pkgs/test/tool/README.md
@@ -0,0 +1,7 @@
+`host.dart` is the source for `lib/src/runner/browser/static/host.dart.js`.
+
+To updated it, run:
+
+```console
+dart compile js tool/host.dart -o lib/src/runner/browser/static/host.dart.js
+```
diff --git a/pkgs/test/tool/host.dart b/pkgs/test/tool/host.dart
new file mode 100644
index 0000000..dc3e5ab
--- /dev/null
+++ b/pkgs/test/tool/host.dart
@@ -0,0 +1,252 @@
+// Copyright (c) 2015, 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.
+
+@JS()
+library;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:js/js.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/src/runner/browser/dom.dart' as dom;
+
+/// A class that exposes the test API to JS.
+///
+/// These are exposed so that tools like IDEs can interact with them via remote
+/// debugging.
+@JS()
+@anonymous
+@staticInterop
+class _JSApi {
+ external factory _JSApi(
+ {void Function() resume, void Function() restartCurrent});
+}
+
+extension _JSApiExtension on _JSApi {
+ /// Causes the test runner to resume running, as though the user had clicked
+ /// the "play" button.
+ // ignore: unused_element
+ external Function get resume;
+
+ /// Causes the test runner to restart the current test once it finishes
+ /// running.
+ // ignore: unused_element
+ external Function get restartCurrent;
+}
+
+/// Sets the top-level `dartTest` object so that it's visible to JS.
+@JS('dartTest')
+external set _jsApi(_JSApi api);
+
+/// The iframes created for each loaded test suite, indexed by the suite id.
+final _iframes = <int, dom.HTMLIFrameElement>{};
+
+/// Subscriptions created for each loaded test suite, indexed by the suite id.
+final _subscriptions = <int, StreamSubscription<void>>{};
+final _domSubscriptions = <int, dom.Subscription>{};
+
+/// The URL for the current page.
+final _currentUrl = Uri.parse(dom.window.location.href);
+
+/// Code that runs in the browser and loads test suites at the server's behest.
+///
+/// One instance of this runs for each browser. When the server tells it to load
+/// a test, it starts an iframe pointing at that test's code; from then on, it
+/// just relays messages between the two.
+///
+/// The browser uses two layers of [MultiChannel]s when communicating with the
+/// server:
+///
+/// server
+/// │
+/// (WebSocket)
+/// │
+/// ┏━ host.html ━━━━━━━━┿━━━━━━━━━━━━━━━━━┓
+/// ┃ │ ┃
+/// ┃ ┌──────┬───MultiChannel─────┐ ┃
+/// ┃ │ │ │ │ │ ┃
+/// ┃ host suite suite suite suite ┃
+/// ┃ │ │ │ │ ┃
+/// ┗━━━━━━━━━━━┿━━━━━━┿━━━━━━┿━━━━━━┿━━━━━┛
+/// │ │ │ │
+/// │ ... ... ...
+/// │
+/// (MessageChannel)
+/// │
+/// ┏━ suite.html (in iframe) ┿━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+/// ┃ │ ┃
+/// ┃ ┌──────────MultiChannel┬─────────┐ ┃
+/// ┃ │ │ │ │ │ ┃
+/// ┃ RemoteListener test test test running test ┃
+/// ┃ ┃
+/// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+///
+/// The host (this code) has a [MultiChannel] that splits the WebSocket
+/// connection with the server. One connection is used for the host itself to
+/// receive messages like "load a suite at this URL", and the rest are
+/// connected to each test suite's iframe via a [MessageChannel].
+///
+/// Each iframe runs a `RemoteListener` which creates its own [MultiChannel] on
+/// top of a [MessageChannel] connection. One connection is used for
+/// the `RemoteListener`, which sends messages like "here are all the tests in
+/// this suite". The rest are used for each test, receiving messages like
+/// "start running". A new connection is also created whenever a test begins
+/// running to send status messages about its progress.
+///
+/// It's of particular note that the suite's [MultiChannel] connection uses the
+/// host's purely as a transport layer; neither is aware that the other is also
+/// using [MultiChannel]. This is necessary, since the host doesn't share memory
+/// with the suites and thus can't share its [MultiChannel] with them, but it
+/// does mean that the server needs to be sure to nest its [MultiChannel]s at
+/// the same place the client does.
+void main() {
+ dom.window.console.log('Dart test runner browser host running');
+ if (_currentUrl.queryParameters['debug'] == 'true') {
+ dom.document.body!.classList.add('debug');
+ }
+
+ runZonedGuarded(() {
+ var serverChannel = _connectToServer();
+ serverChannel.stream.listen((message) {
+ switch (message) {
+ case {
+ 'command': 'loadSuite',
+ 'channel': final num channel,
+ 'url': final String url,
+ 'id': final num id
+ }:
+ var suiteChannel = serverChannel.virtualChannel(channel.toInt());
+ var iframeChannel = _connectToIframe(url, id.toInt());
+ suiteChannel.pipe(iframeChannel);
+ case {'command': 'displayPause'}:
+ dom.document.body!.classList.add('paused');
+ case {'command': 'resume'}:
+ dom.document.body!.classList.remove('paused');
+ case {'command': 'closeSuite', 'id': final id}:
+ _iframes.remove(id)!.remove();
+ _subscriptions.remove(id)?.cancel();
+ _domSubscriptions.remove(id)?.cancel();
+ default:
+ dom.window.console
+ .warn('Unhandled message from test runner: $message');
+ }
+ });
+
+ // Send periodic pings to the test runner so it can know when the browser is
+ // paused for debugging.
+ Timer.periodic(const Duration(seconds: 1),
+ (_) => serverChannel.sink.add({'command': 'ping'}));
+
+ var play = dom.document.querySelector('#play');
+ play!.addEventListener('click', allowInterop((_) {
+ if (!dom.document.body!.classList.contains('paused')) return;
+ dom.document.body!.classList.remove('paused');
+ serverChannel.sink.add({'command': 'resume'});
+ }));
+
+ _jsApi = _JSApi(resume: allowInterop(() {
+ if (!dom.document.body!.classList.contains('paused')) return;
+ dom.document.body!.classList.remove('paused');
+ serverChannel.sink.add({'command': 'resume'});
+ }), restartCurrent: allowInterop(() {
+ serverChannel.sink.add({'command': 'restart'});
+ }));
+ }, (error, stackTrace) {
+ dom.window.console.warn('$error\n${Trace.from(stackTrace).terse}');
+ });
+}
+
+/// Creates a [MultiChannel] connection to the server, using a [WebSocket] as
+/// the underlying protocol.
+MultiChannel<dynamic> _connectToServer() {
+ // The `managerUrl` query parameter contains the WebSocket URL of the remote
+ // [BrowserManager] with which this communicates.
+ var webSocket =
+ dom.createWebSocket(_currentUrl.queryParameters['managerUrl']!);
+
+ var controller = StreamChannelController<Object?>(sync: true);
+ webSocket.addEventListener('message', allowInterop((message) {
+ controller.local.sink
+ .add(jsonDecode((message as dom.MessageEvent).data as String));
+ }));
+
+ controller.local.stream
+ .listen((message) => webSocket.send(jsonEncode(message)));
+
+ return MultiChannel(controller.foreign);
+}
+
+/// Creates an iframe with `src` [url] and expects a message back to connect a
+/// message channel with the suite running in the frame.
+///
+/// [id] identifies the suite loaded in this iframe.
+///
+/// Before the frame is attached, adds a listener for `window.onMessage` which
+/// filters to only the messages coming from this frame (by it's URL) and
+/// expects the first message to be either an initialization message, (coming
+/// from the browser bootstrap message channel initialization), or a map with
+/// the key 'exception' set to true and details in the value for 'data' (coming
+/// from `dart.js` due to a load exception).
+///
+/// Legacy bootstrap implementations send a `{'ready': true}` message as a
+/// signal for this host to create a [MessageChannel] and send the port through
+/// the frame's `window.onMessage` channel.
+///
+/// Upcoming bootstrap implementations will send the string 'port' and include a
+/// port for a prepared [MessageChannel].
+///
+/// Returns a [StreamChannel] which will be connected to the frame once the
+/// message channel port is active.
+StreamChannel<dynamic> _connectToIframe(String url, int id) {
+ var suiteUrl = Uri.parse(url).removeFragment();
+ dom.window.console.log('Starting suite $suiteUrl');
+ var iframe = dom.createHTMLIFrameElement();
+ _iframes[id] = iframe;
+ var controller = StreamChannelController<Object?>(sync: true);
+
+ late dom.Subscription windowSubscription;
+ windowSubscription =
+ dom.Subscription(dom.window, 'message', allowInterop((dom.Event event) {
+ // A message on the Window can theoretically come from any website. It's
+ // very unlikely that a malicious site would care about hacking someone's
+ // unit tests, let alone be able to find the test server while it's
+ // running, but it's good practice to check the origin anyway.
+ var message = event as dom.MessageEvent;
+ if (message.origin != dom.window.location.origin) return;
+ // Disambiguate between frames for different test suites.
+ // Depending on the source type, the `location.href` may be missing.
+ if (message.source.location?.href != iframe.src) return;
+
+ message.stopPropagation();
+ windowSubscription.cancel();
+
+ switch (message.data) {
+ case 'port':
+ dom.window.console.log('Connecting channel for suite $suiteUrl');
+ // The frame is starting and sending a port to forward for the suite.
+ final port = message.ports.first;
+ assert(!_domSubscriptions.containsKey(id));
+ _domSubscriptions[id] =
+ dom.Subscription(port, 'message', allowInterop((event) {
+ controller.local.sink.add((event as dom.MessageEvent).data);
+ }));
+ port.start();
+
+ assert(!_subscriptions.containsKey(id));
+ _subscriptions[id] = controller.local.stream.listen(port.postMessage);
+ case {'exception': true, 'data': final data}:
+ // This message from `dart.js` indicates that an exception occurred
+ // loading the test.
+ controller.local.sink.add(data);
+ }
+ }));
+
+ iframe.src = url;
+ dom.document.body!.appendChild(iframe);
+ dom.window.console.log('Appended iframe with src $url');
+
+ return controller.foreign;
+}
diff --git a/pkgs/test_api/CHANGELOG.md b/pkgs/test_api/CHANGELOG.md
new file mode 100644
index 0000000..98d65ba
--- /dev/null
+++ b/pkgs/test_api/CHANGELOG.md
@@ -0,0 +1,339 @@
+## 0.7.4
+
+* Allow `analyzer: '>=6.0.0 <8.0.0'`
+* Increase SDK constraint to ^3.5.0.
+* Support running Node.js tests compiled with dart2wasm.
+
+## 0.7.3
+
+* Increase SDK constraint to ^3.4.0.
+
+## 0.7.2
+
+* Update min SDK constraint to 3.2.0.
+
+## 0.7.1
+
+- Added [`@doNotSubmit`](https://pub.dev/documentation/meta/latest/meta/doNotSubmit-constant.html) to `test(solo: ...)` and `group(solo: ...)`. In
+ practice, this means that code that was relying on ignoring deprecation
+ warnings and using `solo` or `group` with a `skip` parameter will now fail if
+ `dart analyze --fatal-infos` (or similar) is enabled.
+
+## 0.7.0
+
+- Deprecate `Runtime.internetExplorer`.
+- Added `dart2wasm` as a supported compiler for the `chrome` runtime.
+- **BREAKING**: Removed the `experimentalChromeWasm` runtime.
+- **BREAKING**: Removed `Runtime.isJS` and `Runtime.isWasm`, as this is now
+ based on the compiler and not the runtime.
+
+## 0.6.1
+
+- Drop support for null unsafe Dart, bump SDK constraint to `3.0.0`.
+- Make some implementation classes `final`. These classes were never intended to
+ be extended or implemented. `Metadata`, `PlatformSelector`, `RemoteListener`,
+ `Runtime`, `StackTraceFormatter`, `SuitePlatform`, `RemoteException`,
+ `TestHandle`, `OutstandingWork`, `OutsideTestException`, `OnPlatform`,
+ `Retry`, `Skip`, `Tags`, `TestOn`, `Timeout`.
+- Mark an implementation class `interface`: `StackTraceMapper`.
+- Change the `Compiler` class into an `enum`.
+- Make `Fake` a `mixin class`.
+- Allow the latest analyzer (6.x.x).
+
+## 0.6.0
+
+- Remove the `package:test_api/expect.dart' library. `test`will export from`package:matcher` directly.
+- Fix compatibility with wasm number semantics.
+
+## 0.5.2
+
+- Remove deprecation for the `scaffolding.dart` and `backend.dart` libraries.
+- Export `registerException` from the `scaffolding.dart` library.
+
+## 0.5.1
+
+- Handle a missing `'compiler'` value when running a test compiled against a
+ newer `test_api` than the runner back end is using. The expectation was that
+ the json protocol is only used across packages compatible with the same major
+ version of the `test_api` package, but `flutter test` does not check the
+ version of packages in the pub solve for user test code.
+
+## 0.5.0
+
+- Add `Compiler` class, exposed through `backend.dart`.
+- Support compiler identifiers in platform selectors.
+- Add `compiler` field to `SuitePlatform`. This will become required in the next
+ major release.
+- **BREAKING** Add required `defaultCompiler` and `supportedCompilers` fields
+ to `Runtime`.
+- Add `package:test_api/hooks_testing.dart` library for writing tests against
+ code that uses `package:test_api/hooks.dart`.
+- **BREAKING** Remove `ErrorFormatter`, `expectAsync`, `throws`, and `Throws`
+ from `package:test_api/test_api.dart`.
+
+## 0.4.18
+
+- Don't run `tearDown` until the test body and outstanding work is complete,
+ even if the test has already failed.
+
+## 0.4.17
+
+- Deprecate `throwsNullThrownError`, use `throwsA(isA<TypeError>())` instead.
+ The implementation has been changed to ease migrations.
+- Deprecate `throwsCyclicInitializationError` and replace the implementation
+ with `Throws(TypeMatcher<Error>())`. The specific exception no longer exists
+ and there is no guarantee about what type of error will be thrown.
+
+## 0.4.16
+
+- Add the `experimental-chrome-wasm` runtime. This is very unstable and will
+ eventually be deleted, to be replaced by a `--compiler` flag. See
+ https://github.com/dart-lang/test/issues/1776 for more information on future
+ plans
+- Add `isWasm` field to `Runtime` (defaults to `false`).
+
+## 0.4.15
+
+- Expand the pubspec description.
+- Support `package:matcher` version `0.12.13`.
+
+## 0.4.14
+
+- Require Dart >= 2.18.0
+- Support the latest `package:analyzer`.
+
+## 0.4.13
+
+- Fix `printOnFailure` output to be associated with the correct test.
+
+## 0.4.12
+
+- Internal cleanup.
+
+## 0.4.11
+
+- Support the latest version of `package:matcher`.
+
+## 0.4.10
+
+- Add `Target` to restrict `TestOn` annotation to library level.
+
+## 0.4.9
+
+- Add `ignoreTimeouts` option to `Suite`, which disables all timeouts for all
+ tests in that suite.
+
+## 0.4.8
+
+- `TestFailure` implements `Exception` for compatibility with
+ `only_throw_exceptions`.
+
+## 0.4.7
+
+- Remove logging about enabling the chain-stack-traces flag from the invoker.
+
+## 0.4.6
+
+- Give a better exception when using `markTestSkipped` outside of a test.
+- Format stack traces if a formatter is available when serializing tests
+ and groups from the remote listener.
+
+## 0.4.5
+
+- Add defaulting for older test backends that don't pass a configuration for
+ the `allow_duplicate_test_names` parameter to the remote listener.
+
+## 0.4.4
+
+- Allow disabling duplicate test or group names in the `Declarer`.
+
+## 0.4.3
+
+- Use the latest `package:matcher`.
+
+## 0.4.2
+
+- Update `analyzer` constraint to `>=1.5.0 <3.0.0`.
+
+## 0.4.1
+
+- Give a better error when `printOnFailure` is called from outside a test
+ zone.
+
+## 0.4.0
+
+- Add libraries `scaffolding.dart`, and `expect.dart` to allow importing as
+ subset of the normal surface area.
+- Add new APIs in `hooks.dart` to allow writing custom expectation frameworks
+ which integrate with the test runner.
+- Add examples to `throwsA` and make top-level `throws...` matchers refer to it.
+- Disable stack trace chaining by default.
+- Fix `expectAsync` function type checks.
+- Add `RemoteException`, `RemoteListener`, `StackTraceFormatter`, and
+ `StackTraceMapper` to `backend.dart`.
+- **Breaking** remove `Runtime.phantomJS`
+- **Breaking** Add callback to get the suite channel in the `beforeLoad`
+ callback of `RemoteListener.start`. This is now used in place of using zones
+ to communicate the value.
+
+## 0.3.0
+
+- **Breaking** `TestException.message` is now nullable.
+ - Fixes handling of `null` messages in remote exceptions.
+
+## 0.2.20
+
+- Fix some strong null safety mode errors in the original migration.
+
+## 0.2.19
+
+- Stable release for null safety.
+
+## 0.2.19-nullsafety.7
+
+- Expand upper bound constraints for some null safe migrated packages.
+
+## 0.2.19-nullsafety.6
+
+- Fix `spawnHybridUri` to respect language versioning of the spawned uri.
+
+## 0.2.19-nullsafety.5
+
+- Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+## 0.2.19-nullsafety.4
+
+- Allow prerelease versions of the 2.12 sdk.
+
+## 0.2.19-nullsafety.3
+
+- Add capability to filter to a single exact test name in `Declarer`.
+- Add `markTestSkipped` API.
+
+## 0.2.19-nullsafety.2
+
+- Allow `2.10` stable and `2.11.0-dev` SDKs.
+- Annotate the classes used as annotations to restrict their usage to library
+ level.
+
+## 0.2.19-nullsafety
+
+- Migrate to NNBD.
+ - The vast majority of changes are intended to express the pre-existing
+ behavior of the code regarding to handling of nulls.
+ - **Breaking Change**: `GroupEntry.name` is no longer nullable, the root
+ group now has the empty string as its name.
+- Add the `Fake` class, available through `package:test_api/fake.dart`. This
+ was previously part of the Mockito package, but with null safety it is useful
+ enough that we decided to make it available through `package:test`. In a
+ future release it will be made available directly through
+ `package:test_api/test_api.dart` (and hence through
+ `package:test_core/test_core.dart` and `package:test/test.dart`).
+
+## 0.2.18+1 (Backport)
+
+- Fix `spawnHybridUri` to respect language versioning of the spawned uri.
+
+## 0.2.18
+
+- Update to `matcher` version `0.12.9`.
+
+## 0.2.17
+
+- Add `languageVersionComment` on the `MetaData` class. This should only be
+ present for test suites.
+
+## 0.2.16
+
+- Deprecate `LiveTestController.liveTest`, the `LiveTestController` instance now
+ implements `LiveTest` and can be used directly.
+
+## 0.2.15
+
+- Cancel any StreamQueue that is created as a part of a stream matcher once it
+ is done matching.
+ - This fixes a bug where using a matcher on a custom stream controller and
+ then awaiting the `close()` method on that controller would hang.
+- Avoid causing the test runner to hang if there is a timeout during a
+ `tearDown` callback following a failing test case.
+
+## 0.2.14
+
+- Bump minimum SDK to `2.4.0` for safer usage of for-loop elements.
+
+## 0.2.13
+
+- Work around a bug in the `2.3.0` SDK by avoiding for-loop elements at the top
+ level.
+
+## 0.2.12
+
+- Link to docs on setting timeout when a test times out with the default
+ duration.
+- No longer directly depend on `package:pedantic`.
+
+## 0.2.11
+
+- Extend the timeout for synthetic tests, e.g. `tearDownAll`.
+
+## 0.2.10
+
+- Update to latest `package:matcher`. Improves output for instances of private
+ classes.
+
+## 0.2.9
+
+- Treat non-solo tests as skipped so they are properly reported.
+
+## 0.2.8
+
+- Remove logic which accounted for a race condition in state change. The logic
+ was required because `package:sse` used to not guarantee order. This is no
+ longer the case.
+
+## 0.2.7
+
+- Prepare for upcoming `Stream<List<int>>` changes in the Dart SDK.
+- Mark `package:test_api` as deprecated to prevent accidental use.
+
+## 0.2.6
+
+- Don't swallow exceptions from callbacks in `expectAsync*`.
+- Internal cleanup - fix lints.
+- Fixed a race condition that caused tests to occasionally fail during
+ `tearDownAll` with the message `(tearDownAll) - did not complete [E]`.
+
+## 0.2.5
+
+- Expose the `Metadata`, `PlatformSelector`, `Runtime`, and `SuitePlatform`
+ classes publicly through a new `backend.dart` import.
+
+## 0.2.4
+
+- Allow `stream_channel` version `2.0.0`.
+
+## 0.2.3
+
+- Update to matcher version `0.12.5`.
+
+## 0.2.2
+
+- Require Dart SDK `>=2.1.0`.
+
+## 0.2.1
+
+- Add `remote_listener.dart` and `suite_channel_manager.dart`.
+
+## 0.2.0
+
+- Remove "runner" extensions.
+
+## 0.1.1
+
+- Update `stack_trace_formatter` to fold `test_api` frames by default.
+
+## 0.1.0
+
+- Initial release of `test_api`. Provides the basic API for writing tests and
+ touch points for implementing a custom test runner.
diff --git a/pkgs/test_api/LICENSE b/pkgs/test_api/LICENSE
new file mode 100644
index 0000000..9972f6e
--- /dev/null
+++ b/pkgs/test_api/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2018, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/test_api/README.md b/pkgs/test_api/README.md
new file mode 100644
index 0000000..aa1af49
--- /dev/null
+++ b/pkgs/test_api/README.md
@@ -0,0 +1,8 @@
+[](https://pub.dev/packages/test_api)
+[](https://pub.dev/packages/test_api/publisher)
+
+A minimal package for writing tests. At this time this package is not intended
+to be publicly used as the API will take time to stabilize.
+
+If you're interested in testing Dart code, you likely want to use
+[package:test](https://pub.dev/packages/test).
diff --git a/pkgs/test_api/dart_test.yaml b/pkgs/test_api/dart_test.yaml
new file mode 100644
index 0000000..faff3bb
--- /dev/null
+++ b/pkgs/test_api/dart_test.yaml
@@ -0,0 +1,46 @@
+# Fold frames from helper packages we use in our tests, but not from test
+# itself.
+fold_stack_frames:
+ except:
+ - shelf_test_handler
+ - stream_channel
+ - test_descriptor
+ - test_process
+
+presets:
+ # "-P terse-trace" folds frames from test's implementation to make the output
+ # less verbose when
+ terse-trace:
+ fold_stack_frames:
+ except: [test]
+
+tags:
+ browser:
+ timeout: 2x
+
+ # Browsers can sometimes randomly time out while starting, especially on
+ # Travis which is pretty slow. Don't retry locally because it makes
+ # debugging more annoying.
+ presets: {travis: {retry: 3}}
+
+ dart2js:
+ add_tags: [browser]
+ timeout: 2x
+
+ firefox: {add_tags: [dart2js]}
+ chrome: {add_tags: [dart2js]}
+ phantomjs: {add_tags: [dart2js]}
+
+ safari:
+ add_tags: [dart2js]
+ test_on: mac-os
+
+ # Tests that run pub. These tests may need to be excluded when there are local
+ # dependency_overrides.
+ pub:
+ timeout: 2x
+
+ # Tests that use Node.js. These tests may need to be excluded on systems that
+ # don't have Node installed.
+ node:
+ timeout: 2x
diff --git a/pkgs/test_api/lib/backend.dart b/pkgs/test_api/lib/backend.dart
new file mode 100644
index 0000000..5da47b6
--- /dev/null
+++ b/pkgs/test_api/lib/backend.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2019, 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.
+
+export 'src/backend/compiler.dart' show Compiler;
+export 'src/backend/metadata.dart' show Metadata;
+export 'src/backend/platform_selector.dart' show PlatformSelector;
+export 'src/backend/remote_exception.dart' show RemoteException;
+export 'src/backend/remote_listener.dart' show RemoteListener;
+export 'src/backend/runtime.dart' show Runtime;
+export 'src/backend/stack_trace_formatter.dart' show StackTraceFormatter;
+export 'src/backend/stack_trace_mapper.dart' show StackTraceMapper;
+export 'src/backend/suite_platform.dart' show SuitePlatform;
diff --git a/pkgs/test_api/lib/fake.dart b/pkgs/test_api/lib/fake.dart
new file mode 100644
index 0000000..f8b2d80
--- /dev/null
+++ b/pkgs/test_api/lib/fake.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, 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.
+
+// Note: eventually we would like to fold this into test_api.dart, but we can't
+// do so until Mockito stops implementing its own version of `Fake`, because
+// there is code in the wild that imports both test_api.dart and Mockito.
+
+@Deprecated('package:test_api is not intended for general use. '
+ 'Please use package:test.')
+library;
+
+export 'src/frontend/fake.dart';
diff --git a/pkgs/test_api/lib/hooks.dart b/pkgs/test_api/lib/hooks.dart
new file mode 100644
index 0000000..126d322
--- /dev/null
+++ b/pkgs/test_api/lib/hooks.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2021, 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 'package:stack_trace/stack_trace.dart';
+
+import 'src/backend/closed_exception.dart';
+import 'src/backend/invoker.dart';
+import 'src/backend/stack_trace_formatter.dart';
+
+export 'src/backend/test_failure.dart' show TestFailure;
+export 'src/scaffolding/utils.dart' show pumpEventQueue;
+
+final class TestHandle {
+ /// Returns handle for the currently running test.
+ ///
+ /// This must be called from within the zone that the test is running in. If
+ /// the current zone is not a test's zone throws [OutsideTestException].
+ static TestHandle get current {
+ final invoker = Invoker.current;
+ if (invoker == null) throw OutsideTestException();
+ return TestHandle._(
+ invoker, StackTraceFormatter.current ?? _defaultFormatter);
+ }
+
+ static final _defaultFormatter = StackTraceFormatter();
+
+ final Invoker _invoker;
+ final StackTraceFormatter _stackTraceFormatter;
+ TestHandle._(this._invoker, this._stackTraceFormatter);
+
+ String get name => _invoker.liveTest.test.name;
+
+ /// Whether this test has already completed successfully.
+ ///
+ /// If a callback originating from a test case is invoked after the test has
+ /// already passed it may be an indication of a test that fails to wait for
+ /// all work to be finished, or of an asynchronous callback that is called
+ /// more times or later than expected.
+ bool get shouldBeDone => _invoker.liveTest.state.shouldBeDone;
+
+ /// Marks this test as skipped.
+ ///
+ /// A skipped test may still fail if any exception is thrown, including
+ /// uncaught asynchronous errors.
+ void markSkipped(String message) {
+ if (_invoker.closed) throw ClosedException();
+ _invoker.skip(message);
+ }
+
+ /// Indicates that this test should not be considered done until the returned
+ /// [OutstandingWork] is marked as complete.
+ ///
+ /// The test may time out before the outstanding work completes.
+ OutstandingWork markPending() {
+ if (_invoker.closed) throw ClosedException();
+ return OutstandingWork._(_invoker, Zone.current);
+ }
+
+ /// Converts [stackTrace] to a [Chain] according to the current test's
+ /// configuration.
+ Chain formatStackTrace(StackTrace stackTrace) =>
+ _stackTraceFormatter.formatStackTrace(stackTrace);
+}
+
+final class OutstandingWork {
+ final Invoker _invoker;
+ final Zone _zone;
+ var _isComplete = false;
+ OutstandingWork._(this._invoker, this._zone) {
+ _invoker.addOutstandingCallback();
+ }
+ void complete() {
+ if (_isComplete) return;
+ _isComplete = true;
+ _zone.run(_invoker.removeOutstandingCallback);
+ }
+}
+
+final class OutsideTestException implements Exception {}
diff --git a/pkgs/test_api/lib/hooks_testing.dart b/pkgs/test_api/lib/hooks_testing.dart
new file mode 100644
index 0000000..b0a6154
--- /dev/null
+++ b/pkgs/test_api/lib/hooks_testing.dart
@@ -0,0 +1,157 @@
+// 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 'src/backend/group.dart';
+import 'src/backend/invoker.dart';
+import 'src/backend/live_test.dart';
+import 'src/backend/metadata.dart';
+import 'src/backend/runtime.dart';
+import 'src/backend/state.dart';
+import 'src/backend/suite.dart';
+import 'src/backend/suite_platform.dart';
+
+@Deprecated('These classes are unused for monitoring tests. Use `State`.')
+export 'src/backend/state.dart' show Result, Status;
+export 'src/backend/test_failure.dart' show TestFailure;
+
+/// A monitor for the behavior of a callback when it is run as the body of a
+/// test case.
+///
+/// Allows running a callback as the body of a local test case and querying for
+/// the current [state], and [errors], and subscribing to future errors.
+///
+/// Use [run] to run a test body and query for the success or failure.
+///
+/// Use [start] to start a test and query for whether it has finished running.
+final class TestCaseMonitor {
+ final LiveTest _liveTest;
+ final _done = Completer<void>();
+ TestCaseMonitor._(FutureOr<void> Function() body)
+ : _liveTest = _createTest(body);
+
+ /// Run [body] as a test case and return a [TestCaseMonitor] with the result.
+ ///
+ /// The [state] will either [State.passed], [State.skipped], or
+ /// [State.failed], the test will no longer be running.
+ ///
+ /// {@template result-late-fail}
+ /// Note that a test can change state from [State.passed] to [State.failed]
+ /// if the test surfaces an unawaited asynchronous error.
+ /// {@endtemplate}
+ ///
+ /// ```dart
+ /// final monitor = await TestCaseMonitor.run(() {
+ /// fail('oh no!');
+ /// });
+ /// assert(monitor.state == State.failed);
+ /// assert((monitor.errors.single.error as TestFailure).message == 'oh no!');
+ /// ```
+ static Future<TestCaseMonitor> run(FutureOr<void> Function() body) async {
+ final monitor = TestCaseMonitor.start(body);
+ await monitor.onDone;
+ return monitor;
+ }
+
+ /// Start [body] as a test case and return a [TestCaseMonitor] with the status
+ /// and result.
+ ///
+ /// The [state] will start as [State.pending] if queried synchronously, but it
+ /// will switch to [State.running]. After `onDone` completes the state will be
+ /// one of [State.passed], [State.skipped], or [State.failed].
+ ///
+ /// {@macro result-late-fail}
+ ///
+ /// ```dart
+ /// late void Function() completeWork;
+ /// final monitor = TestCaseMonitor.start(() {
+ /// final outstandingWork = TestHandle.current.markPending();
+ /// completeWork = outstandingWork.complete;
+ /// });
+ /// await pumpEventQueue();
+ /// assert(monitor.state == State.running);
+ /// completeWork();
+ /// await monitor.onDone;
+ /// assert(monitor.state == State.passed);
+ /// ```
+ static TestCaseMonitor start(FutureOr<void> Function() body) =>
+ TestCaseMonitor._(body).._start();
+
+ void _start() {
+ _liveTest.run().whenComplete(_done.complete);
+ }
+
+ /// A future that completes after this test has finished running, or has
+ /// surfaced an error.
+ Future<void> get onDone => _done.future;
+
+ /// The running and success or failure status for the test case.
+ State get state {
+ final status = _liveTest.state.status;
+ if (status == Status.pending) return State.pending;
+ if (status == Status.running) return State.running;
+ final result = _liveTest.state.result;
+ if (result == Result.skipped) return State.skipped;
+ if (result == Result.success) return State.passed;
+ // result == Result.failure || result == Result.error
+ return State.failed;
+ }
+
+ /// The errors surfaced by the test.
+ ///
+ /// A test with any errors will have a [state] of [State.failed].
+ ///
+ /// {@macro result-late-fail}
+ ///
+ /// A test may have more than one error if there were unhandled asynchronous
+ /// errors surfaced after the test is done.
+ Iterable<AsyncError> get errors => _liveTest.errors;
+
+ /// A stream of errors surfaced by the test.
+ ///
+ /// This stream will not close, asynchronous errors may be surfaced within the
+ /// test's error zone at any point.
+ Stream<AsyncError> get onError => _liveTest.onError;
+}
+
+/// Returns a local [LiveTest] that runs [body].
+LiveTest _createTest(FutureOr<void> Function() body) {
+ var test = LocalTest('test', Metadata(chainStackTraces: true), body);
+ var suite = Suite(Group.root([test]), _suitePlatform, ignoreTimeouts: false);
+ return test.load(suite);
+}
+
+/// A dummy suite platform to use for testing suites.
+final _suitePlatform =
+ SuitePlatform(Runtime.vm, compiler: Runtime.vm.defaultCompiler);
+
+/// The running and success state of a test monitored by a [TestCaseMonitor].
+enum State {
+ /// The test is has not yet started.
+ pending,
+
+ /// The test is running and has not yet failed.
+ running,
+
+ /// The test has completed without any error.
+ ///
+ /// This implies that the test body has completed, and no error has surfaced
+ /// *yet*. However, it this doesn't mean that the test won't fail in the
+ /// future.
+ passed,
+
+ /// The test, or some part of it, has been skipped.
+ ///
+ /// This does not imply that the test has not had an error, but if there are
+ /// errors they are ignored.
+ skipped,
+
+ /// The test has failed.
+ ///
+ /// An test fails when any exception, typically a [TestFailure], is thrown in
+ /// the test's zone. A test that has failed may still have additional errors
+ /// that surface as unhandled asynchronous errors.
+ failed,
+}
diff --git a/pkgs/test_api/lib/scaffolding.dart b/pkgs/test_api/lib/scaffolding.dart
new file mode 100644
index 0000000..9d864bb
--- /dev/null
+++ b/pkgs/test_api/lib/scaffolding.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2021, 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.
+
+/// {@canonicalFor on_platform.OnPlatform}
+/// {@canonicalFor retry.Retry}
+/// {@canonicalFor skip.Skip}
+/// {@canonicalFor tags.Tags}
+/// {@canonicalFor test_on.TestOn}
+/// {@canonicalFor timeout.Timeout}
+library;
+
+export 'src/backend/configuration/on_platform.dart' show OnPlatform;
+export 'src/backend/configuration/retry.dart' show Retry;
+export 'src/backend/configuration/skip.dart' show Skip;
+export 'src/backend/configuration/tags.dart' show Tags;
+export 'src/backend/configuration/test_on.dart' show TestOn;
+export 'src/backend/configuration/timeout.dart' show Timeout;
+export 'src/scaffolding/spawn_hybrid.dart' show spawnHybridCode, spawnHybridUri;
+export 'src/scaffolding/test_structure.dart'
+ show addTearDown, group, setUp, setUpAll, tearDown, tearDownAll, test;
+export 'src/scaffolding/utils.dart'
+ show markTestSkipped, printOnFailure, pumpEventQueue, registerException;
diff --git a/pkgs/test_api/lib/src/backend/closed_exception.dart b/pkgs/test_api/lib/src/backend/closed_exception.dart
new file mode 100644
index 0000000..08a45a0
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/closed_exception.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2015, 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.
+
+/// An exception thrown by various front-end methods when the test framework has
+/// been closed and a test must shut down as soon as possible.
+class ClosedException implements Exception {
+ ClosedException();
+
+ @override
+ String toString() => 'This test has been closed.';
+}
diff --git a/pkgs/test_api/lib/src/backend/compiler.dart b/pkgs/test_api/lib/src/backend/compiler.dart
new file mode 100644
index 0000000..41c253d
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/compiler.dart
@@ -0,0 +1,59 @@
+// 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.
+
+/// All the Dart compilers supported by the test runner.
+enum Compiler {
+ /// The production Dart to Javascript compiler (whole world, optimizing).
+ dart2js('Dart2Js', 'dart2js'),
+
+ /// Experimental Dart to Wasm compiler.
+ dart2wasm('Dart2Wasm', 'dart2wasm'),
+
+ /// Compiles dart code to a native executable.
+ exe('Exe', 'exe'),
+
+ /// The standard compiler for vm tests, compiles tests to kernel before
+ /// running them on the VM.
+ kernel('Kernel', 'kernel'),
+
+ /// Runs tests directly from source, with no precompilation.
+ source('Source', 'source');
+
+ /// The compilers that are supported by the test runner by default.
+ static const List<Compiler> builtIn = [
+ Compiler.dart2js,
+ Compiler.dart2wasm,
+ Compiler.exe,
+ Compiler.kernel,
+ Compiler.source,
+ ];
+
+ /// The human-friendly name of the compiler.
+ final String name;
+
+ /// The identifier used to look up the compiler.
+ final String identifier;
+
+ const Compiler(this.name, this.identifier);
+
+ /// Converts a JSON-safe representation generated by [serialize] back into a
+ /// [Compiler].
+ ///
+ /// Note that custom [Compiler] implementations are not supported.
+ factory Compiler.deserialize(Object serialized) => builtIn
+ .firstWhere((compiler) => compiler.identifier == serialized as String);
+
+ /// Converts [this] into a JSON-safe object that can be converted back to a
+ /// [Compiler] using [Compiler.deserialize].
+ Object serialize() => identifier;
+
+ @override
+ String toString() => name;
+}
+
+extension CompileTarget on Compiler {
+ bool get isJS => this == Compiler.dart2js;
+
+ bool get isWasm => this == Compiler.dart2wasm;
+}
diff --git a/pkgs/test_api/lib/src/backend/configuration/on_platform.dart b/pkgs/test_api/lib/src/backend/configuration/on_platform.dart
new file mode 100644
index 0000000..02a5254
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/configuration/on_platform.dart
@@ -0,0 +1,17 @@
+// Copyright (c) 2015, 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 'package:meta/meta_meta.dart';
+
+/// An annotation for platform-specific customizations for a test suite.
+///
+/// See [the README][onPlatform].
+///
+/// [onPlatform]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-specific-configuration
+@Target({TargetKind.library})
+final class OnPlatform {
+ final Map<String, dynamic> annotationsByPlatform;
+
+ const OnPlatform(this.annotationsByPlatform);
+}
diff --git a/pkgs/test_api/lib/src/backend/configuration/retry.dart b/pkgs/test_api/lib/src/backend/configuration/retry.dart
new file mode 100644
index 0000000..1811814
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/configuration/retry.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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 'package:meta/meta_meta.dart';
+
+/// An annotation for marking a test suite to be retried.
+///
+/// A suite-level retry configuration will enable retries for every test in the
+/// suite, unless the group or test is configured with a more specific retry.
+@Target({TargetKind.library})
+final class Retry {
+ /// The number of times the tests in the suite will be retried.
+ final int count;
+
+ /// Marks all tests in a test suite to be retried.
+ const Retry(this.count);
+}
diff --git a/pkgs/test_api/lib/src/backend/configuration/skip.dart b/pkgs/test_api/lib/src/backend/configuration/skip.dart
new file mode 100644
index 0000000..3927994
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/configuration/skip.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2015, 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 'package:meta/meta_meta.dart';
+
+/// An annotation for marking a test suite as skipped.
+@Target({TargetKind.library})
+final class Skip {
+ /// The reason the test suite is skipped, or `null` if no reason is given.
+ final String? reason;
+
+ /// Marks a suite as skipped.
+ ///
+ /// If [reason] is passed, it's included in the test output as the reason the
+ /// test is skipped.
+ const Skip([this.reason]);
+}
diff --git a/pkgs/test_api/lib/src/backend/configuration/tags.dart b/pkgs/test_api/lib/src/backend/configuration/tags.dart
new file mode 100644
index 0000000..dbd03f3
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/configuration/tags.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2015, 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 'package:meta/meta_meta.dart';
+
+/// An annotation for applying a set of user-defined tags to a test suite.
+///
+/// See [the documentation on tagging tests][tagging tests].
+///
+/// [tagging tests]: https://github.com/dart-lang/test/blob/master/pkgs/test/README.md#tagging-tests
+@Target({TargetKind.library})
+final class Tags {
+ /// The tags for the test suite.
+ Set<String> get tags => _tags.toSet();
+
+ final Iterable<String> _tags;
+
+ /// Applies a set of user-defined tags to a test suite.
+ const Tags(this._tags);
+}
diff --git a/pkgs/test_api/lib/src/backend/configuration/test_on.dart b/pkgs/test_api/lib/src/backend/configuration/test_on.dart
new file mode 100644
index 0000000..dee7f40
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/configuration/test_on.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2015, 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 'package:meta/meta_meta.dart';
+
+/// An annotation indicating which platforms a test suite supports.
+///
+/// For the full syntax of [expression], see [the README][].
+///
+/// [the README]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-selectors
+@Target({TargetKind.library})
+final class TestOn {
+ /// The expression specifying the platform.
+ final String expression;
+
+ const TestOn(this.expression);
+}
diff --git a/pkgs/test_api/lib/src/backend/configuration/timeout.dart b/pkgs/test_api/lib/src/backend/configuration/timeout.dart
new file mode 100644
index 0000000..e51d520
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/configuration/timeout.dart
@@ -0,0 +1,155 @@
+// Copyright (c) 2015, 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 'package:meta/meta_meta.dart';
+import 'package:string_scanner/string_scanner.dart';
+
+/// A regular expression that matches text until a letter or whitespace.
+///
+/// This is intended to scan through a number without actually encoding the full
+/// Dart number grammar. It doesn't stop on "e" because that can be a component
+/// of numbers.
+final _untilUnit = RegExp(r'[^a-df-z\s]+', caseSensitive: false);
+
+/// A regular expression that matches a time unit.
+final _unit = RegExp(r'([um]s|[dhms])', caseSensitive: false);
+
+/// A regular expression that matches a section of whitespace.
+final _whitespace = RegExp(r'\s+');
+
+/// A class representing a modification to the default timeout for a test.
+///
+/// By default, a test will time out after 30 seconds. With [Timeout], that
+/// can be overridden entirely; with [Timeout.factor], it can be scaled
+/// relative to the default.
+@Target({TargetKind.library})
+final class Timeout {
+ /// A constant indicating that a test should never time out.
+ static const none = Timeout._none();
+
+ /// The timeout duration.
+ ///
+ /// If set, this overrides the default duration entirely. It's `null` for
+ /// timeouts with a non-null [scaleFactor] and for [Timeout.none].
+ final Duration? duration;
+
+ /// The timeout factor.
+ ///
+ /// The default timeout will be multiplied by this to get the new timeout.
+ /// Thus a factor of 2 means that the test will take twice as long to time
+ /// out, and a factor of 0.5 means that it will time out twice as quickly.
+ ///
+ /// This is `null` for timeouts with a non-null [duration] and for
+ /// [Timeout.none].
+ final num? scaleFactor;
+
+ /// Declares an absolute timeout that overrides the default.
+ const Timeout(this.duration) : scaleFactor = null;
+
+ /// Declares a relative timeout that scales the default.
+ const Timeout.factor(this.scaleFactor) : duration = null;
+
+ const Timeout._none()
+ : scaleFactor = null,
+ duration = null;
+
+ /// Parse the timeout from a user-provided string.
+ ///
+ /// This supports the following formats:
+ ///
+ /// * `Number "x"`, which produces a relative timeout with the given scale
+ /// factor.
+ ///
+ /// * `(Number ("d" | "h" | "m" | "s" | "ms" | "us") (" ")?)+`, which produces
+ /// an absolute timeout with the duration given by the sum of the given
+ /// units.
+ ///
+ /// * `"none"`, which produces [Timeout.none].
+ ///
+ /// Throws a [FormatException] if [timeout] is not in a valid format
+ factory Timeout.parse(String timeout) {
+ var scanner = StringScanner(timeout);
+
+ // First check for the string "none".
+ if (scanner.scan('none')) {
+ scanner.expectDone();
+ return Timeout.none;
+ }
+
+ // Scan a number. This will be either a time unit or a scale factor.
+ scanner.expect(_untilUnit, name: 'number');
+ var number = double.parse(scanner.lastMatch![0]!);
+
+ // A number followed by "x" is a scale factor.
+ if (scanner.scan('x') || scanner.scan('X')) {
+ scanner.expectDone();
+ return Timeout.factor(number);
+ }
+
+ // Parse time units until none are left. The condition is in the middle of
+ // the loop because we've already parsed the first number.
+ var microseconds = 0.0;
+ while (true) {
+ scanner.expect(_unit, name: 'unit');
+ microseconds += _microsecondsFor(number, scanner.lastMatch![0]!);
+
+ scanner.scan(_whitespace);
+
+ // Scan the next number, if it's available.
+ if (!scanner.scan(_untilUnit)) break;
+ number = double.parse(scanner.lastMatch![0]!);
+ }
+
+ scanner.expectDone();
+ return Timeout(Duration(microseconds: microseconds.round()));
+ }
+
+ /// Returns the number of microseconds in [number] [unit]s.
+ static double _microsecondsFor(double number, String unit) => switch (unit) {
+ 'd' => number * 24 * 60 * 60 * 1000000,
+ 'h' => number * 60 * 60 * 1000000,
+ 'm' => number * 60 * 1000000,
+ 's' => number * 1000000,
+ 'ms' => number * 1000,
+ 'us' => number,
+ _ => throw ArgumentError('Unknown unit $unit.'),
+ };
+
+ /// Returns a new [Timeout] that merges [this] with [other].
+ ///
+ /// [Timeout.none] takes precedence over everything. If timeout is
+ /// [Timeout.none] and [other] declares a [duration], that takes precedence.
+ /// Otherwise, this timeout's [duration] or [factor] are multiplied by
+ /// [other]'s [factor].
+ Timeout merge(Timeout other) {
+ if (this == none || other == none) return none;
+ if (other.duration != null) return Timeout(other.duration);
+ if (duration != null) return Timeout(duration! * other.scaleFactor!);
+ return Timeout.factor(scaleFactor! * other.scaleFactor!);
+ }
+
+ /// Returns a new [Duration] from applying [this] to [base].
+ ///
+ /// If this is [none], returns `null`.
+ Duration? apply(Duration base) {
+ if (this == none) return null;
+ return duration ?? base * scaleFactor!;
+ }
+
+ @override
+ int get hashCode => duration.hashCode ^ 5 * scaleFactor.hashCode;
+
+ @override
+ bool operator ==(Object other) =>
+ other is Timeout &&
+ other.duration == duration &&
+ other.scaleFactor == scaleFactor;
+
+ @override
+ String toString() {
+ if (duration != null) return duration.toString();
+ if (scaleFactor != null) return '${scaleFactor}x';
+ return 'none';
+ }
+}
diff --git a/pkgs/test_api/lib/src/backend/declarer.dart b/pkgs/test_api/lib/src/backend/declarer.dart
new file mode 100644
index 0000000..76f7563
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/declarer.dart
@@ -0,0 +1,433 @@
+// Copyright (c) 2015, 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 'package:collection/collection.dart';
+import 'package:stack_trace/stack_trace.dart';
+
+import 'configuration/timeout.dart';
+import 'group.dart';
+import 'group_entry.dart';
+import 'invoker.dart';
+import 'metadata.dart';
+import 'test.dart';
+
+/// A class that manages the state of tests as they're declared.
+///
+/// A nested tree of Declarers tracks the current group, set-up, and tear-down
+/// functions. Each Declarer in the tree corresponds to a group. This tree is
+/// tracked by a zone-scoped "current" Declarer; the current declarer can be set
+/// for a block using [Declarer.declare], and it can be accessed using
+/// [Declarer.current].
+class Declarer {
+ /// The parent declarer, or `null` if this corresponds to the root group.
+ final Declarer? _parent;
+
+ /// The name of the current test group, including the name of any parent
+ /// groups.
+ ///
+ /// This is `null` if this is the root group.
+ final String? _name;
+
+ /// The metadata for this group, including the metadata of any parent groups
+ /// and of the test suite.
+ final Metadata _metadata;
+
+ /// The set of variables that are valid for platform selectors, in addition to
+ /// the built-in variables that are allowed everywhere.
+ final Set<String> _platformVariables;
+
+ /// The stack trace for this group.
+ ///
+ /// This is `null` for the root (implicit) group.
+ final Trace? _trace;
+
+ /// Whether to collect stack traces for [GroupEntry]s.
+ final bool _collectTraces;
+
+ /// Whether to disable retries of tests.
+ final bool _noRetry;
+
+ /// The set-up functions to run for each test in this group.
+ final _setUps = <dynamic Function()>[];
+
+ /// The tear-down functions to run for each test in this group.
+ final _tearDowns = <dynamic Function()>[];
+
+ /// The set-up functions to run once for this group.
+ final _setUpAlls = <dynamic Function()>[];
+
+ /// The default timeout for synthetic tests.
+ final _timeout = const Timeout(Duration(minutes: 12));
+
+ /// The trace for the first call to [setUpAll].
+ ///
+ /// All [setUpAll]s are run in a single logical test, so they can only have
+ /// one trace. The first trace is most often correct, since the first
+ /// [setUpAll] is always run and the rest are only run if that one succeeds.
+ Trace? _setUpAllTrace;
+
+ /// The tear-down functions to run once for this group.
+ final _tearDownAlls = <void Function()>[];
+
+ /// The trace for the first call to [tearDownAll].
+ ///
+ /// All [tearDownAll]s are run in a single logical test, so they can only have
+ /// one trace. The first trace matches [_setUpAllTrace].
+ Trace? _tearDownAllTrace;
+
+ /// The children of this group, either tests or sub-groups.
+ ///
+ /// All modifications to this must go through [_addEntry].
+ final _entries = <GroupEntry>[];
+
+ /// Whether [build] has been called for this declarer.
+ bool _built = false;
+
+ /// The tests and/or groups that have been flagged as solo.
+ final _soloEntries = <GroupEntry>[];
+
+ /// Whether any tests and/or groups have been flagged as solo.
+ bool get _solo => _soloEntries.isNotEmpty;
+
+ /// An exact full test name to match.
+ ///
+ /// When non-null only tests with exactly this name will be considered. The
+ /// full test name is the combination of the test case name with all group
+ /// prefixes. All other tests, including their metadata like `solo`, is
+ /// ignored. Uniqueness is not guaranteed so this may match more than one
+ /// test.
+ ///
+ /// Groups which are not a strict prefix of this name will be ignored.
+ final String? _fullTestName;
+
+ /// The current zone-scoped declarer.
+ static Declarer? get current => Zone.current[#test.declarer] as Declarer?;
+
+ /// All the test and group names that have been declared in the entire suite.
+ ///
+ /// If duplicate test names are allowed, this is not tracked and it will be
+ /// `null`.
+ final Set<String>? _seenNames;
+
+ /// Whether this declarer is running in a standalone test executation.
+ ///
+ /// The full test runner awaits asynchronous `main` declarations, and so
+ /// asynchronous work can be performed in between calls to `group`, and `test`
+ /// etc. When running as a standalone file tests are run synchronously
+ /// following the first call to declare a test, so all tests must be declared
+ /// synchronously starting at that point. Track whether we are running in this
+ /// more limited mode to customize the error message for tests declared late.
+ final bool _isStandalone;
+
+ /// Creates a new declarer for the root group.
+ ///
+ /// This is the implicit group that exists outside of any calls to `group()`.
+ /// If [metadata] is passed, it's used as the metadata for the implicit root
+ /// group.
+ ///
+ /// The [platformVariables] are the set of variables that are valid for
+ /// platform selectors in test and group metadata, in addition to the built-in
+ /// variables that are allowed everywhere.
+ ///
+ /// If [collectTraces] is `true`, this will set [GroupEntry.trace] for all
+ /// entries built by the declarer. Note that this can be noticeably slow when
+ /// thousands of tests are being declared (see #457).
+ ///
+ /// If [noRetry] is `true` tests will be run at most once.
+ ///
+ /// If [allowDuplicateTestNames] is `false`, then a
+ /// [DuplicateTestNameException] will be thrown if two tests (or groups) have
+ /// the same name.
+ Declarer({
+ Metadata? metadata,
+ Set<String>? platformVariables,
+ bool collectTraces = false,
+ bool noRetry = false,
+ String? fullTestName,
+ // TODO: Change the default https://github.com/dart-lang/test/issues/1571
+ bool allowDuplicateTestNames = true,
+ bool isStandalone = false,
+ }) : this._(
+ null,
+ null,
+ metadata ?? Metadata(),
+ platformVariables ?? const UnmodifiableSetView.empty(),
+ collectTraces,
+ null,
+ noRetry,
+ fullTestName,
+ allowDuplicateTestNames ? null : <String>{},
+ isStandalone,
+ );
+
+ Declarer._(
+ this._parent,
+ this._name,
+ this._metadata,
+ this._platformVariables,
+ this._collectTraces,
+ this._trace,
+ this._noRetry,
+ this._fullTestName,
+ this._seenNames,
+ this._isStandalone,
+ );
+
+ /// Runs [body] with this declarer as [Declarer.current].
+ ///
+ /// Returns the return value of [body].
+ T declare<T>(T Function() body) =>
+ runZoned(body, zoneValues: {#test.declarer: this});
+
+ /// Defines a test case with the given name and body.
+ void test(String name, dynamic Function() body,
+ {String? testOn,
+ Timeout? timeout,
+ Object? skip,
+ Map<String, dynamic>? onPlatform,
+ Object? tags,
+ int? retry,
+ bool solo = false}) {
+ _checkNotBuilt('test');
+
+ final fullName = _prefix(name);
+ if (_fullTestName != null && fullName != _fullTestName) {
+ return;
+ }
+
+ var newMetadata = Metadata.parse(
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ onPlatform: onPlatform,
+ tags: tags,
+ retry: _noRetry ? 0 : retry);
+ newMetadata.validatePlatformSelectors(_platformVariables);
+ var metadata = _metadata.merge(newMetadata);
+ _addEntry(LocalTest(fullName, metadata, () async {
+ var parents = <Declarer>[];
+ for (Declarer? declarer = this;
+ declarer != null;
+ declarer = declarer._parent) {
+ parents.add(declarer);
+ }
+
+ // Register all tear-down functions in all declarers. Iterate through
+ // parents outside-in so that the Invoker gets the functions in the order
+ // they were declared in source.
+ for (var declarer in parents.reversed) {
+ for (var tearDown in declarer._tearDowns) {
+ Invoker.current!.addTearDown(tearDown);
+ }
+ }
+
+ await runZoned(() async {
+ await _runSetUps();
+ await body();
+ },
+ // Make the declarer visible to running tests so that they'll throw
+ // useful errors when calling `test()` and `group()` within a test.
+ zoneValues: {#test.declarer: this});
+ }, trace: _collectTraces ? Trace.current(2) : null, guarded: false));
+
+ if (solo) {
+ _soloEntries.add(_entries.last);
+ }
+ }
+
+ /// Creates a group of tests.
+ void group(String name, void Function() body,
+ {String? testOn,
+ Timeout? timeout,
+ Object? skip,
+ Map<String, dynamic>? onPlatform,
+ Object? tags,
+ int? retry,
+ bool solo = false}) {
+ _checkNotBuilt('group');
+
+ final fullTestPrefix = _prefix(name);
+ if (_fullTestName != null && !_fullTestName.startsWith(fullTestPrefix)) {
+ return;
+ }
+
+ var newMetadata = Metadata.parse(
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ onPlatform: onPlatform,
+ tags: tags,
+ retry: _noRetry ? 0 : retry);
+ newMetadata.validatePlatformSelectors(_platformVariables);
+ var metadata = _metadata.merge(newMetadata);
+ var trace = _collectTraces ? Trace.current(2) : null;
+
+ var declarer = Declarer._(
+ this,
+ fullTestPrefix,
+ metadata,
+ _platformVariables,
+ _collectTraces,
+ trace,
+ _noRetry,
+ _fullTestName,
+ _seenNames,
+ _isStandalone,
+ );
+ declarer.declare(() {
+ // Cast to dynamic to avoid the analyzer complaining about us using the
+ // result of a void method.
+ var result = (body as dynamic)();
+ if (result is! Future) return;
+ throw ArgumentError('Groups may not be async.');
+ });
+ _addEntry(declarer.build());
+
+ if (solo || declarer._solo) {
+ _soloEntries.add(_entries.last);
+ }
+ }
+
+ /// Returns [name] prefixed with this declarer's group name.
+ String _prefix(String name) => _name == null ? name : '$_name $name';
+
+ /// Registers a function to be run before each test in this group.
+ void setUp(dynamic Function() callback) {
+ _checkNotBuilt('setUp');
+ _setUps.add(callback);
+ }
+
+ /// Registers a function to be run after each test in this group.
+ void tearDown(dynamic Function() callback) {
+ _checkNotBuilt('tearDown');
+ _tearDowns.add(callback);
+ }
+
+ /// Registers a function to be run once before all tests.
+ void setUpAll(dynamic Function() callback) {
+ _checkNotBuilt('setUpAll');
+ if (_collectTraces) _setUpAllTrace ??= Trace.current(2);
+ _setUpAlls.add(callback);
+ }
+
+ /// Registers a function to be run once after all tests.
+ void tearDownAll(dynamic Function() callback) {
+ _checkNotBuilt('tearDownAll');
+ if (_collectTraces) _tearDownAllTrace ??= Trace.current(2);
+ _tearDownAlls.add(callback);
+ }
+
+ /// Like [tearDownAll], but called from within a running [setUpAll] test to
+ /// dynamically add a [tearDownAll].
+ void addTearDownAll(dynamic Function() callback) =>
+ _tearDownAlls.add(callback);
+
+ /// Finalizes and returns the group being declared.
+ ///
+ /// **Note**: The tests in this group must be run in a [Invoker.guard]
+ /// context; otherwise, test errors won't be captured.
+ Group build() {
+ _checkNotBuilt('build');
+
+ _built = true;
+ var entries = _entries.map((entry) {
+ if (_solo && !_soloEntries.contains(entry)) {
+ entry = LocalTest(
+ entry.name,
+ entry.metadata
+ .change(skip: true, skipReason: 'does not have "solo"'),
+ () {});
+ }
+ return entry;
+ }).toList();
+
+ return Group(_name ?? '', entries,
+ metadata: _metadata,
+ trace: _trace,
+ setUpAll: _setUpAll,
+ tearDownAll: _tearDownAll);
+ }
+
+ /// Throws a [StateError] if [build] has been called.
+ ///
+ /// [name] should be the name of the method being called.
+ void _checkNotBuilt(String name) {
+ if (!_built) return;
+ final restrictionMessage = _isStandalone
+ ? 'When running a test as an executable directly '
+ '(not as a suite by the test runner), '
+ 'tests must be declared in a synchronous block.\n'
+ 'If async work is required before any tests are run '
+ 'use a `setUpAll` callback.\n'
+ 'If async work cannot be avoided before declaring tests, '
+ 'all async events must be complete before declaring the first test.'
+ : 'If async work is required before any tests are run '
+ 'use a `setUpAll` callback.\n'
+ 'If async work cannot be avoided before declaring tests it must '
+ 'all be awaited within the Future returned from `main`.';
+ throw StateError("Can't call $name() once tests have begun running.\n"
+ '$restrictionMessage');
+ }
+
+ /// Run the set-up functions for this and any parent groups.
+ ///
+ /// If no set-up functions are declared, this returns a [Future] that
+ /// completes immediately.
+ Future _runSetUps() async {
+ if (_parent != null) await _parent._runSetUps();
+ // TODO: why does type inference not work here?
+ await Future.forEach<Function>(_setUps, (setUp) => setUp());
+ }
+
+ /// Returns a [Test] that runs the callbacks in [_setUpAll], or `null`.
+ Test? get _setUpAll {
+ if (_setUpAlls.isEmpty) return null;
+
+ return LocalTest(_prefix('(setUpAll)'), _metadata.change(timeout: _timeout),
+ () {
+ return runZoned(
+ () => Future.forEach<Function>(_setUpAlls, (setUp) => setUp()),
+ // Make the declarer visible to running scaffolds so they can add to
+ // the declarer's `tearDownAll()` list.
+ zoneValues: {#test.declarer: this});
+ }, trace: _setUpAllTrace, guarded: false, isScaffoldAll: true);
+ }
+
+ /// Returns a [Test] that runs the callbacks in [_tearDownAll], or `null`.
+ Test? get _tearDownAll {
+ // We have to create a tearDownAll if there's a setUpAll, since it might
+ // dynamically add tear-down code using [addTearDownAll].
+ if (_setUpAlls.isEmpty && _tearDownAlls.isEmpty) return null;
+
+ return LocalTest(
+ _prefix('(tearDownAll)'), _metadata.change(timeout: _timeout), () {
+ return runZoned(() => Invoker.current!.runTearDowns(_tearDownAlls),
+ // Make the declarer visible to running scaffolds so they can add to
+ // the declarer's `tearDownAll()` list.
+ zoneValues: {#test.declarer: this});
+ }, trace: _tearDownAllTrace, guarded: false, isScaffoldAll: true);
+ }
+
+ void _addEntry(GroupEntry entry) {
+ if (_seenNames?.add(entry.name) == false) {
+ throw DuplicateTestNameException(entry.name);
+ }
+ _entries.add(entry);
+ }
+}
+
+/// An exception thrown when two test cases in the same test suite (same `main`)
+/// have an identical name.
+class DuplicateTestNameException implements Exception {
+ final String name;
+ DuplicateTestNameException(this.name);
+
+ @override
+ String toString() => 'A test with the name "$name" was already declared. '
+ 'Test cases must have unique names.\n\n'
+ 'See https://github.com/dart-lang/test/blob/master/pkgs/test/doc/'
+ 'configuration.md#allow_test_randomization for info on enabling this.';
+}
diff --git a/pkgs/test_api/lib/src/backend/group.dart b/pkgs/test_api/lib/src/backend/group.dart
new file mode 100644
index 0000000..9a28875
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/group.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+
+import 'group_entry.dart';
+import 'metadata.dart';
+import 'suite_platform.dart';
+import 'test.dart';
+
+/// A group contains one or more tests and subgroups.
+///
+/// It includes metadata that applies to all contained tests.
+class Group implements GroupEntry {
+ @override
+ final String name;
+
+ @override
+ final Metadata metadata;
+
+ @override
+ final Trace? trace;
+
+ /// The children of this group.
+ final List<GroupEntry> entries;
+
+ /// Returns a new root-level group.
+ Group.root(Iterable<GroupEntry> entries, {Metadata? metadata})
+ : this('', entries, metadata: metadata);
+
+ /// A test to run before all tests in the group.
+ ///
+ /// This is `null` if no `setUpAll` callbacks were declared.
+ final Test? setUpAll;
+
+ /// A test to run after all tests in the group.
+ ///
+ /// This is `null` if no `tearDown` callbacks were declared.
+ final Test? tearDownAll;
+
+ /// The number of tests (recursively) in this group.
+ int get testCount {
+ if (_testCount != null) return _testCount!;
+ _testCount = entries.fold<int>(
+ 0, (count, entry) => count + (entry is Group ? entry.testCount : 1));
+ return _testCount!;
+ }
+
+ int? _testCount;
+
+ Group(this.name, Iterable<GroupEntry> entries,
+ {Metadata? metadata, this.trace, this.setUpAll, this.tearDownAll})
+ : entries = List<GroupEntry>.unmodifiable(entries),
+ metadata = metadata ?? Metadata();
+
+ @override
+ Group? forPlatform(SuitePlatform platform) {
+ if (!metadata.testOn.evaluate(platform)) return null;
+ var newMetadata = metadata.forPlatform(platform);
+ var filtered = _map((entry) => entry.forPlatform(platform));
+ if (filtered.isEmpty && entries.isNotEmpty) return null;
+ return Group(name, filtered,
+ metadata: newMetadata,
+ trace: trace,
+ setUpAll: setUpAll,
+ tearDownAll: tearDownAll);
+ }
+
+ @override
+ Group? filter(bool Function(Test) callback) {
+ var filtered = _map((entry) => entry.filter(callback));
+ if (filtered.isEmpty && entries.isNotEmpty) return null;
+ return Group(name, filtered,
+ metadata: metadata,
+ trace: trace,
+ setUpAll: setUpAll,
+ tearDownAll: tearDownAll);
+ }
+
+ /// Returns the entries of this group mapped using [callback].
+ ///
+ /// Any `null` values returned by [callback] will be removed.
+ List<GroupEntry> _map(GroupEntry? Function(GroupEntry) callback) {
+ return entries
+ .map((entry) => callback(entry))
+ .whereType<GroupEntry>()
+ .toList();
+ }
+}
diff --git a/pkgs/test_api/lib/src/backend/group_entry.dart b/pkgs/test_api/lib/src/backend/group_entry.dart
new file mode 100644
index 0000000..a6f30bd
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/group_entry.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+
+import 'metadata.dart';
+import 'suite_platform.dart';
+import 'test.dart';
+
+/// A [Test] or [Group].
+abstract class GroupEntry {
+ /// The name of the entry, including the prefixes from any containing
+ /// [Group]s.
+ ///
+ /// This will be empty for the root group.
+ String get name;
+
+ /// The metadata for the entry, including the metadata from any containing
+ /// [Group]s.
+ Metadata get metadata;
+
+ /// The stack trace for the call to `test()` or `group()` that defined this
+ /// entry, or `null` if the entry was defined in a different way.
+ Trace? get trace;
+
+ /// Returns a copy of [this] with all platform-specific metadata resolved.
+ ///
+ /// Removes any tests and groups with [Metadata.testOn] selectors that don't
+ /// match [platform]. Returns `null` if this entry's selector doesn't match.
+ GroupEntry? forPlatform(SuitePlatform platform);
+
+ /// Returns a copy of [this] with all tests that don't match [callback]
+ /// removed.
+ ///
+ /// Returns `null` if this is a test that doesn't match [callback] or a group
+ /// where no child tests match [callback].
+ GroupEntry? filter(bool Function(Test) callback);
+}
diff --git a/pkgs/test_api/lib/src/backend/invoker.dart b/pkgs/test_api/lib/src/backend/invoker.dart
new file mode 100644
index 0000000..58f1001
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/invoker.dart
@@ -0,0 +1,464 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+
+import 'closed_exception.dart';
+import 'declarer.dart';
+import 'group.dart';
+import 'live_test.dart';
+import 'live_test_controller.dart';
+import 'message.dart';
+import 'metadata.dart';
+import 'state.dart';
+import 'suite.dart';
+import 'suite_platform.dart';
+import 'test.dart';
+import 'test_failure.dart';
+import 'util/pretty_print.dart';
+
+/// A test in this isolate.
+class LocalTest extends Test {
+ @override
+ final String name;
+
+ @override
+ final Metadata metadata;
+
+ @override
+ final Trace? trace;
+
+ /// Whether this is a test defined using `setUpAll()` or `tearDownAll()`.
+ final bool isScaffoldAll;
+
+ /// The test body.
+ final void Function() _body;
+
+ /// Whether the test is run in its own error zone.
+ final bool _guarded;
+
+ /// Creates a new [LocalTest].
+ ///
+ /// If [guarded] is `true`, the test is run in its own error zone, and any
+ /// errors that escape that zone cause the test to fail. If it's `false`, it's
+ /// the caller's responsibility to invoke [LiveTest.run] in the context of a
+ /// call to [Invoker.guard].
+ LocalTest(this.name, this.metadata, this._body,
+ {this.trace, bool guarded = true, this.isScaffoldAll = false})
+ : _guarded = guarded;
+
+ LocalTest._(this.name, this.metadata, this._body, this.trace, this._guarded,
+ this.isScaffoldAll);
+
+ /// Loads a single runnable instance of this test.
+ @override
+ LiveTest load(Suite suite, {Iterable<Group>? groups}) {
+ var invoker = Invoker._(suite, this, groups: groups, guarded: _guarded);
+ return invoker.liveTest;
+ }
+
+ @override
+ Test? forPlatform(SuitePlatform platform) {
+ if (!metadata.testOn.evaluate(platform)) return null;
+ return LocalTest._(name, metadata.forPlatform(platform), _body, trace,
+ _guarded, isScaffoldAll);
+ }
+}
+
+/// The class responsible for managing the lifecycle of a single local test.
+///
+/// The current invoker is accessible within the zone scope of the running test
+/// using [Invoker.current]. It's used to track asynchronous callbacks and
+/// report asynchronous errors.
+class Invoker {
+ /// The live test being driven by the invoker.
+ ///
+ /// This provides a view into the state of the test being executed.
+ LiveTest get liveTest => _controller;
+ late final LiveTestController _controller;
+
+ /// Whether to run this test in its own error zone.
+ final bool _guarded;
+
+ /// Whether the user code is allowed to interact with the invoker despite it
+ /// being closed.
+ ///
+ /// A test is generally closed because the runner is shutting down (in
+ /// response to a signal) or because the test's suite is finished.
+ /// Typically calls to [addTearDown] and [addOutstandingCallback] are only
+ /// allowed before the test is closed. Tear down callbacks, however, are
+ /// allowed to perform these interactions to facilitate resource cleanup on a
+ /// best-effort basis, so the invoker is made to appear open only within the
+ /// zones running the teardown callbacks.
+ bool get _forceOpen => Zone.current[_forceOpenForTearDownKey] as bool;
+
+ /// An opaque object used as a key in the zone value map to identify
+ /// [_forceOpen].
+ ///
+ /// This is an instance variable to ensure that multiple invokers don't step
+ /// on one anothers' toes.
+ final _forceOpenForTearDownKey = Object();
+
+ /// Whether the test has been closed.
+ ///
+ /// Once the test is closed, [expect] and [expectAsync] will throw
+ /// [ClosedException]s whenever accessed to help the test stop executing as
+ /// soon as possible.
+ bool get closed => !_forceOpen && _onCloseCompleter.isCompleted;
+
+ /// A future that completes once the test has been closed.
+ Future<void> get onClose => _onCloseCompleter.future;
+ final _onCloseCompleter = Completer<void>();
+
+ /// The test being run.
+ LocalTest get _test => liveTest.test as LocalTest;
+
+ /// The outstanding callback counter for the current zone.
+ _AsyncCounter get _outstandingCallbacks {
+ var counter = Zone.current[_counterKey] as _AsyncCounter?;
+ if (counter != null) return counter;
+ throw StateError("Can't add or remove outstanding callbacks outside "
+ 'of a test body.');
+ }
+
+ /// All the zones created by [_waitForOutstandingCallbacks], in the order they
+ /// were created.
+ ///
+ /// This is used to throw timeout errors in the most recent zone.
+ final _outstandingCallbackZones = <Zone>[];
+
+ /// An opaque object used as a key in the zone value map to identify
+ /// [_outstandingCallbacks].
+ ///
+ /// This is an instance variable to ensure that multiple invokers don't step
+ /// on one anothers' toes.
+ final _counterKey = Object();
+
+ /// The number of times this [liveTest] has been run.
+ int _runCount = 0;
+
+ /// The current invoker, or `null` if none is defined.
+ ///
+ /// An invoker is only set within the zone scope of a running test.
+ static Invoker? get current {
+ // TODO(nweiz): Use a private symbol when dart2js supports it (issue 17526).
+ return Zone.current[#test.invoker] as Invoker?;
+ }
+
+ /// Runs [callback] in a zone where unhandled errors from [LiveTest]s are
+ /// caught and dispatched to the appropriate [Invoker].
+ static T? guard<T>(T Function() callback) =>
+ runZoned<T?>(callback, zoneSpecification: ZoneSpecification(
+ // Use [handleUncaughtError] rather than [onError] so we can
+ // capture [zone] and with it the outstanding callback counter for
+ // the zone in which [error] was thrown.
+ handleUncaughtError: (self, _, zone, error, stackTrace) {
+ var invoker = zone[#test.invoker] as Invoker?;
+ if (invoker != null) {
+ self.parent!.run(() => invoker._handleError(zone, error, stackTrace));
+ } else {
+ self.parent!.handleUncaughtError(error, stackTrace);
+ }
+ }));
+
+ /// The timer for tracking timeouts.
+ ///
+ /// This will be `null` until the test starts running.
+ Timer? _timeoutTimer;
+
+ /// The tear-down functions to run when this test finishes.
+ final _tearDowns = <void Function()>[];
+
+ /// Messages to print if and when this test fails.
+ final _printsOnFailure = <String>[];
+
+ Invoker._(Suite suite, LocalTest test,
+ {Iterable<Group>? groups, bool guarded = true})
+ : _guarded = guarded {
+ _controller = LiveTestController(
+ suite, test, _onRun, _onCloseCompleter.complete,
+ groups: groups);
+ }
+
+ /// Runs [callback] after this test completes.
+ ///
+ /// The [callback] may return a [Future]. Like all tear-downs, callbacks are
+ /// run in the reverse of the order they're declared.
+ void addTearDown(dynamic Function() callback) {
+ if (closed) throw ClosedException();
+
+ if (_test.isScaffoldAll) {
+ Declarer.current!.addTearDownAll(callback);
+ } else {
+ _tearDowns.add(callback);
+ }
+ }
+
+ /// Tells the invoker that there's a callback running that it should wait for
+ /// before considering the test successful.
+ ///
+ /// Each call to [addOutstandingCallback] should be followed by a call to
+ /// [removeOutstandingCallback] once the callback is no longer running. Note
+ /// that only successful tests wait for outstanding callbacks; as soon as a
+ /// test experiences an error, any further calls to [addOutstandingCallback]
+ /// or [removeOutstandingCallback] will do nothing.
+ ///
+ /// Throws a [ClosedException] if this test has been closed.
+ void addOutstandingCallback() {
+ if (closed) throw ClosedException();
+ _outstandingCallbacks.increment();
+ }
+
+ /// Tells the invoker that a callback declared with [addOutstandingCallback]
+ /// is no longer running.
+ void removeOutstandingCallback() {
+ heartbeat();
+ _outstandingCallbacks.decrement();
+ }
+
+ /// Run [tearDowns] in reverse order.
+ ///
+ /// An exception thrown in a tearDown callback will cause the test to fail, if
+ /// it isn't already failing, but it won't prevent the remaining callbacks
+ /// from running. This invoker will not be closeable within the zone that the
+ /// teardowns are running in.
+ Future<void> runTearDowns(List<FutureOr<void> Function()> tearDowns) {
+ heartbeat();
+ return runZoned(() async {
+ while (tearDowns.isNotEmpty) {
+ var completer = Completer<void>();
+
+ addOutstandingCallback();
+ _waitForOutstandingCallbacks(() {
+ Future.sync(tearDowns.removeLast()).whenComplete(completer.complete);
+ }).then((_) => removeOutstandingCallback()).unawaited;
+
+ await completer.future;
+ }
+ }, zoneValues: {_forceOpenForTearDownKey: true});
+ }
+
+ /// Runs [fn] and completes once [fn] and all outstanding callbacks registered
+ /// within [fn] have completed.
+ ///
+ /// Outstanding callbacks registered within [fn] will *not* be registered as
+ /// outstanding callback outside of [fn].
+ Future<void> _waitForOutstandingCallbacks(FutureOr<void> Function() fn) {
+ heartbeat();
+
+ Zone? zone;
+ var counter = _AsyncCounter();
+ runZoned(() async {
+ zone = Zone.current;
+ _outstandingCallbackZones.add(zone!);
+ try {
+ await fn();
+ } finally {
+ counter.decrement();
+ }
+ }, zoneValues: {_counterKey: counter});
+
+ return counter.onZero.whenComplete(() {
+ _outstandingCallbackZones.remove(zone!);
+ });
+ }
+
+ /// Notifies the invoker that progress is being made.
+ ///
+ /// Each heartbeat resets the timeout timer. This helps ensure that
+ /// long-running tests that still make progress don't time out.
+ void heartbeat() {
+ if (liveTest.isComplete) return;
+ if (_timeoutTimer != null) _timeoutTimer!.cancel();
+ if (liveTest.suite.ignoreTimeouts == true) return;
+
+ const defaultTimeout = Duration(seconds: 30);
+ var timeout = liveTest.test.metadata.timeout.apply(defaultTimeout);
+ if (timeout == null) return;
+ String message() {
+ var message = 'Test timed out after ${niceDuration(timeout)}.';
+ if (timeout == defaultTimeout) {
+ message += ' See https://pub.dev/packages/test#timeouts';
+ }
+ return message;
+ }
+
+ _timeoutTimer = Zone.root.createTimer(timeout, () {
+ _outstandingCallbackZones.last.run(() {
+ _handleError(Zone.current, TimeoutException(message(), timeout));
+ _outstandingCallbacks.complete();
+ });
+ });
+ }
+
+ /// Marks the current test as skipped.
+ ///
+ /// If passed, [message] is emitted as a skip message.
+ ///
+ /// Note that this *does not* mark the test as complete. That is, it sets
+ /// the result to [Result.skipped], but doesn't change the state.
+ void skip([String? message]) {
+ if (liveTest.state.shouldBeDone) {
+ // Set the state explicitly so we don't get an extra error about the test
+ // failing after being complete.
+ _controller.setState(const State(Status.complete, Result.error));
+ throw 'This test was marked as skipped after it had already completed.\n'
+ 'Make sure to use a matching library which informs the test runner\n'
+ 'of pending async work.';
+ }
+
+ if (message != null) _controller.message(Message.skip(message));
+ // TODO: error if the test is already complete.
+ _controller.setState(const State(Status.pending, Result.skipped));
+ }
+
+ /// Prints [message] if and when this test fails.
+ void printOnFailure(String message) {
+ message = message.trim();
+ if (liveTest.state.result.isFailing) {
+ _print('\n$message');
+ } else {
+ _printsOnFailure.add(message);
+ }
+ }
+
+ /// Notifies the invoker of an asynchronous error.
+ ///
+ /// The [zone] is the zone in which the error was thrown.
+ void _handleError(Zone zone, Object error, [StackTrace? stackTrace]) {
+ // Ignore errors propagated from previous test runs
+ if (_runCount != zone[#runCount]) return;
+
+ // Get the chain information from the zone in which the error was thrown.
+ zone.run(() {
+ if (stackTrace == null) {
+ stackTrace = Chain.current();
+ } else {
+ stackTrace = Chain.forTrace(stackTrace!);
+ }
+ });
+
+ // Store these here because they'll change when we set the state below.
+ var shouldBeDone = liveTest.state.shouldBeDone;
+
+ if (error is! TestFailure) {
+ _controller.setState(const State(Status.complete, Result.error));
+ } else if (liveTest.state.result != Result.error) {
+ _controller.setState(const State(Status.complete, Result.failure));
+ }
+
+ _controller.addError(error, stackTrace!);
+
+ if (_printsOnFailure.isNotEmpty) {
+ _print(_printsOnFailure.join('\n\n'));
+ _printsOnFailure.clear();
+ }
+
+ // If a test was supposed to be done but then had an error, that indicates
+ // that it was poorly-written and could be flaky.
+ if (!shouldBeDone) return;
+
+ // However, users don't think of load tests as "tests", so the error isn't
+ // helpful for them.
+ if (liveTest.suite.isLoadSuite) return;
+
+ _handleError(
+ zone,
+ 'This test failed after it had already completed.\n'
+ 'Make sure to use a matching library which informs the test runner\n'
+ 'of pending async work.',
+ stackTrace);
+ }
+
+ /// The method that's run when the test is started.
+ void _onRun() {
+ _controller.setState(const State(Status.running, Result.success));
+
+ _runCount++;
+ Chain.capture(() {
+ _guardIfGuarded(() {
+ runZoned(() async {
+ // Run the test asynchronously so that the "running" state change
+ // has a chance to hit its event handler(s) before the test produces
+ // an error. If an error is emitted before the first state change is
+ // handled, we can end up with [onError] callbacks firing before the
+ // corresponding [onStateChange], which violates the timing
+ // guarantees.
+ //
+ // Use the event loop over the microtask queue to avoid starvation.
+ await Future(() {});
+
+ await _waitForOutstandingCallbacks(_test._body);
+ await _waitForOutstandingCallbacks(() => runTearDowns(_tearDowns));
+
+ if (_timeoutTimer != null) _timeoutTimer!.cancel();
+
+ if (liveTest.state.result != Result.success &&
+ _runCount < liveTest.test.metadata.retry + 1) {
+ _controller.message(Message.print('Retry: ${liveTest.test.name}'));
+ _onRun();
+ return;
+ }
+
+ _controller.setState(State(Status.complete, liveTest.state.result));
+
+ _controller.completer.complete();
+ },
+ zoneValues: {
+ #test.invoker: this,
+ _forceOpenForTearDownKey: false,
+ #runCount: _runCount,
+ },
+ zoneSpecification:
+ ZoneSpecification(print: (_, __, ___, line) => _print(line)));
+ });
+ }, when: liveTest.test.metadata.chainStackTraces, errorZone: false);
+ }
+
+ /// Runs [callback], in a [Invoker.guard] context if [_guarded] is `true`.
+ void _guardIfGuarded(void Function() callback) {
+ if (_guarded) {
+ Invoker.guard(callback);
+ } else {
+ callback();
+ }
+ }
+
+ /// Prints [text] as a message to [_controller].
+ void _print(String text) => _controller.message(Message.print(text));
+}
+
+/// A manually incremented/decremented counter that completes a [Future] the
+/// first time it reaches zero or is forcefully completed.
+class _AsyncCounter {
+ var _count = 1;
+
+ /// A Future that completes the first time the counter reaches 0.
+ Future<void> get onZero => _completer.future;
+ final _completer = Completer<void>();
+
+ void increment() {
+ _count++;
+ }
+
+ void decrement() {
+ _count--;
+ if (_count != 0) return;
+ if (_completer.isCompleted) return;
+ _completer.complete();
+ }
+
+ /// Force [onZero] to complete.
+ ///
+ /// No effect if [onZero] has already completed.
+ void complete() {
+ if (!_completer.isCompleted) _completer.complete();
+ }
+}
+
+extension<T> on Future<T> {
+ void get unawaited {}
+}
diff --git a/pkgs/test_api/lib/src/backend/live_test.dart b/pkgs/test_api/lib/src/backend/live_test.dart
new file mode 100644
index 0000000..683e87c
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/live_test.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2015, 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 'group.dart';
+import 'message.dart';
+import 'state.dart';
+import 'suite.dart';
+import 'test.dart';
+
+/// A runnable instance of a test.
+///
+/// This is distinct from [Test] in order to keep [Test] immutable. Running a
+/// test requires state, and [LiveTest] provides a view of the state of the test
+/// as it runs.
+///
+/// If the state changes, [state] will be updated before [onStateChange] fires.
+/// Likewise, if an error is caught, it will be added to [errors] before being
+/// emitted via [onError]. If an error causes a state change, [onStateChange]
+/// will fire before [onError]. If an error or other state change causes the
+/// test to complete, [onComplete] will complete after [onStateChange] and
+/// [onError] fire.
+abstract class LiveTest {
+ /// The suite within which this test is being run.
+ Suite get suite;
+
+ /// The groups within which this test is being run, from the outermost to the
+ /// innermost.
+ ///
+ /// This will always contain at least the implicit top-level group.
+ List<Group> get groups;
+
+ /// The running test.
+ Test get test;
+
+ /// The current state of the running test.
+ ///
+ /// This starts as [Status.pending] and [Result.success]. It will be updated
+ /// before [onStateChange] fires.
+ ///
+ /// Note that even if this is marked [Status.complete], the test may still be
+ /// running code asynchronously. A test is considered complete either once it
+ /// hits its first error or when all [expectAsync] callbacks have been called
+ /// and any returned [Future] has completed, but it's possible for further
+ /// processing to happen, which may cause further errors. It's even possible
+ /// for a test that was marked [Status.complete] and [Result.success] to be
+ /// marked as [Result.error] later.
+ State get state;
+
+ /// Returns whether this test has completed.
+ ///
+ /// This is equivalent to [state.status] being [Status.complete].
+ ///
+ /// Note that even if this returns `true`, the test may still be
+ /// running code asynchronously. A test is considered complete either once it
+ /// hits its first error or when all [expectAsync] callbacks have been called
+ /// and any returned [Future] has completed, but it's possible for further
+ /// processing to happen, which may cause further errors.
+ bool get isComplete => state.status == Status.complete;
+
+ // A stream that emits a new [State] whenever [state] changes.
+ //
+ // This will only ever emit a [State] if it's different than the previous
+ // [state]. It will emit an event after [state] has been updated. Note that
+ // since this is an asynchronous stream, it's possible for [state] not to
+ // match the [State] that it emits within the [Stream.listen] callback.
+ Stream<State> get onStateChange;
+
+ /// An unmodifiable list of all errors that have been caught while running
+ /// this test.
+ ///
+ /// This will be updated before [onError] fires. These errors are not
+ /// guaranteed to have the same types as when they were thrown; for example,
+ /// they may need to be serialized across isolate boundaries. The stack traces
+ /// will be [Chain]s.
+ List<AsyncError> get errors;
+
+ /// A stream that emits a new [AsyncError] whenever an error is caught.
+ ///
+ /// This will emit an event after [errors] is updated. These errors are not
+ /// guaranteed to have the same types as when they were thrown; for example,
+ /// they may need to be serialized across isolate boundaries. The stack traces
+ /// will be [Chain]s.
+ Stream<AsyncError> get onError;
+
+ /// A stream that emits messages produced by the test.
+ Stream<Message> get onMessage;
+
+ /// A [Future] that completes once the test is complete.
+ ///
+ /// This will complete after [onStateChange] has fired, and after [onError]
+ /// has fired if the test completes because of an error. It's the same as the
+ /// [Future] returned by [run].
+ ///
+ /// Note that even once this completes, the test may still be running code
+ /// asynchronously. A test is considered complete either once it hits its
+ /// first error or when all [expectAsync] callbacks have been called and any
+ /// returned [Future] has completed, but it's possible for further processing
+ /// to happen, which may cause further errors.
+ Future get onComplete;
+
+ /// The name of this live test without any group prefixes.
+ String get individualName {
+ var group = groups.last;
+ if (group.name.isEmpty) return test.name;
+ if (!test.name.startsWith(group.name)) return test.name;
+
+ // The test will have the same name as the group for virtual tests created
+ // to represent skipping the entire group.
+ if (test.name.length == group.name.length) return '';
+
+ return test.name.substring(group.name.length + 1);
+ }
+
+ /// Loads a copy of this [LiveTest] that's able to be run again.
+ LiveTest copy() => test.load(suite, groups: groups);
+
+ /// Signals that this test should start running as soon as possible.
+ ///
+ /// A test may not start running immediately for various reasons specific to
+ /// the means by which it's defined. Until it starts running, [state] will
+ /// continue to be marked [Status.pending].
+ ///
+ /// This returns the same [Future] as [onComplete]. It may not be called more
+ /// than once.
+ Future run();
+
+ /// Signals that this test should stop emitting events and release any
+ /// resources it may have allocated.
+ ///
+ /// Once [close] is called, [onComplete] will complete if it hasn't already
+ /// and [onStateChange] and [onError] will close immediately. This means that,
+ /// if the test was running at the time [close] is called, it will never emit
+ /// a [Status.complete] state-change event. Once a test is closed, [expect]
+ /// and [expectAsync] will throw a [ClosedException] to help the test
+ /// terminate as quickly as possible.
+ ///
+ /// This doesn't automatically happen after the test completes because there
+ /// may be more asynchronous work going on in the background that could
+ /// produce new errors.
+ ///
+ /// Returns a [Future] that completes once all resources are released *and*
+ /// the test has completed. This allows the caller to wait until the test's
+ /// tear-down logic has run.
+ Future close();
+}
diff --git a/pkgs/test_api/lib/src/backend/live_test_controller.dart b/pkgs/test_api/lib/src/backend/live_test_controller.dart
new file mode 100644
index 0000000..956c293
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/live_test_controller.dart
@@ -0,0 +1,178 @@
+// Copyright (c) 2015, 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:collection';
+
+import 'package:stack_trace/stack_trace.dart';
+
+import 'group.dart';
+import 'live_test.dart';
+import 'message.dart';
+import 'state.dart';
+import 'suite.dart';
+import 'test.dart';
+
+/// A concrete [LiveTest] that enforces some lifecycle guarantees.
+///
+/// This automatically handles some of [LiveTest]'s guarantees, but for the most
+/// part it's the caller's responsibility to make sure everything gets
+/// dispatched in the correct order.
+class LiveTestController extends LiveTest {
+ @Deprecated('Use this instance instead')
+ LiveTest get liveTest => this;
+
+ @override
+ final Suite suite;
+
+ @override
+ final List<Group> groups;
+
+ @override
+ final Test test;
+
+ /// The function that will actually start the test running.
+ final void Function() _onRun;
+
+ /// A function to run when the test is closed.
+ ///
+ /// This may be `null`.
+ final void Function() _onClose;
+
+ /// The list of errors caught by the test.
+ final _errors = <AsyncError>[];
+
+ @override
+ List<AsyncError> get errors => UnmodifiableListView(_errors);
+
+ /// The current state of the test.
+ @override
+ State state = const State(Status.pending, Result.success);
+
+ /// The controller for [onStateChange].
+ ///
+ /// This is synchronous to ensure that events are well-ordered across multiple
+ /// streams.
+ final _onStateChange = StreamController<State>.broadcast(sync: true);
+ @override
+ Stream<State> get onStateChange => _onStateChange.stream;
+
+ /// The controller for [onError].
+ ///
+ /// This is synchronous to ensure that events are well-ordered across multiple
+ /// streams.
+ final _onError = StreamController<AsyncError>.broadcast(sync: true);
+ @override
+ Stream<AsyncError> get onError => _onError.stream;
+
+ /// The controller for [onMessage].
+ ///
+ /// This is synchronous to ensure that events are well-ordered across multiple
+ /// streams.
+ final _onMessage = StreamController<Message>.broadcast(sync: true);
+ @override
+ Stream<Message> get onMessage => _onMessage.stream;
+
+ final completer = Completer<void>();
+
+ /// Whether [run] has been called.
+ var _runCalled = false;
+
+ /// Whether [close] has been called.
+ bool get _isClosed => _onError.isClosed;
+
+ /// Creates a new controller for a [LiveTest].
+ ///
+ /// [test] is the test being run; [suite] is the suite that contains it.
+ ///
+ /// [onRun] is a function that's called from [LiveTest.run]. It should start
+ /// the test running. The controller takes care of ensuring that
+ /// [LiveTest.run] isn't called more than once and that [LiveTest.onComplete]
+ /// is returned.
+ ///
+ /// [onClose] is a function that's called the first time [LiveTest.close] is
+ /// called. It should clean up any resources that have been allocated for the
+ /// test and ensure that the test finishes quickly if it's still running. It
+ /// will only be called if [onRun] has been called first.
+ ///
+ /// If [groups] is passed, it's used to populate the list of groups that
+ /// contain this test. Otherwise, `suite.group` is used.
+ LiveTestController(this.suite, this.test, this._onRun, this._onClose,
+ {Iterable<Group>? groups})
+ : groups = groups == null ? [suite.group] : List.unmodifiable(groups);
+
+ /// Adds an error to the [LiveTest].
+ ///
+ /// This both adds the error to [LiveTest.errors] and emits it via
+ /// [LiveTest.onError]. [stackTrace] is automatically converted into a [Chain]
+ /// if it's not one already.
+ void addError(Object error, StackTrace? stackTrace) {
+ if (_isClosed) return;
+
+ var asyncError = AsyncError(
+ error, Chain.forTrace(stackTrace ?? StackTrace.fromString('')));
+ _errors.add(asyncError);
+ _onError.add(asyncError);
+ }
+
+ /// Sets the current state of the [LiveTest] to [newState].
+ ///
+ /// If [newState] is different than the old state, this both sets
+ /// [LiveTest.state] and emits the new state via [LiveTest.onStateChanged]. If
+ /// it's not different, this does nothing.
+ void setState(State newState) {
+ if (_isClosed) return;
+ if (state == newState) return;
+
+ state = newState;
+ _onStateChange.add(newState);
+ }
+
+ /// Emits message over [LiveTest.onMessage].
+ void message(Message message) {
+ if (_onMessage.hasListener) {
+ _onMessage.add(message);
+ } else {
+ // Make sure all messages get surfaced one way or another to aid in
+ // debugging.
+ Zone.root.print(message.text);
+ }
+ }
+
+ @override
+ Future<void> run() {
+ if (_runCalled) {
+ throw StateError('LiveTest.run() may not be called more than once.');
+ } else if (_isClosed) {
+ throw StateError('LiveTest.run() may not be called for a closed '
+ 'test.');
+ }
+ _runCalled = true;
+
+ _onRun();
+ return onComplete;
+ }
+
+ /// Returns a future that completes when the test is complete.
+ ///
+ /// We also wait for the state to transition to Status.complete.
+ @override
+ Future<void> get onComplete => completer.future;
+
+ @override
+ Future<void> close() {
+ if (_isClosed) return onComplete;
+
+ _onStateChange.close();
+ _onError.close();
+
+ if (_runCalled) {
+ _onClose();
+ } else {
+ completer.complete();
+ }
+
+ return onComplete;
+ }
+}
diff --git a/pkgs/test_api/lib/src/backend/message.dart b/pkgs/test_api/lib/src/backend/message.dart
new file mode 100644
index 0000000..a027460
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/message.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2016, 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.
+
+/// A message emitted by a test.
+///
+/// A message encompasses any textual information that should be presented to
+/// the user. Reporters are encouraged to visually distinguish different message
+/// types.
+class Message {
+ final MessageType type;
+
+ final String text;
+
+ Message(this.type, this.text);
+
+ Message.print(this.text) : type = MessageType.print;
+ Message.skip(this.text) : type = MessageType.skip;
+}
+
+class MessageType {
+ /// A message explicitly printed by the user's test.
+ static const print = MessageType._('print');
+
+ /// A message indicating that a test, or some portion of one, was skipped.
+ static const skip = MessageType._('skip');
+
+ /// The name of the message type.
+ final String name;
+
+ factory MessageType.parse(String name) => switch (name) {
+ 'print' => MessageType.print,
+ 'skip' => MessageType.skip,
+ _ => throw ArgumentError('Invalid message type "$name".'),
+ };
+
+ const MessageType._(this.name);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/test_api/lib/src/backend/metadata.dart b/pkgs/test_api/lib/src/backend/metadata.dart
new file mode 100644
index 0000000..d0663b1
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/metadata.dart
@@ -0,0 +1,420 @@
+// Copyright (c) 2015, 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 'package:boolean_selector/boolean_selector.dart';
+import 'package:collection/collection.dart';
+
+import 'configuration/skip.dart';
+import 'configuration/timeout.dart';
+import 'platform_selector.dart';
+import 'suite_platform.dart';
+import 'util/identifier_regex.dart';
+import 'util/pretty_print.dart';
+
+/// Metadata for a test or test suite.
+///
+/// This metadata comes from declarations on the test itself; it doesn't include
+/// configuration from the user.
+final class Metadata {
+ /// Empty metadata with only default values.
+ ///
+ /// Using this is slightly more efficient than manually constructing a new
+ /// metadata with no arguments.
+ static final empty = Metadata._();
+
+ /// The selector indicating which platforms the suite supports.
+ final PlatformSelector testOn;
+
+ /// The modification to the timeout for the test or suite.
+ final Timeout timeout;
+
+ /// Whether the test or suite should be skipped.
+ bool get skip => _skip ?? false;
+ final bool? _skip;
+
+ /// The reason the test or suite should be skipped, if given.
+ final String? skipReason;
+
+ /// Whether to use verbose stack traces.
+ bool get verboseTrace => _verboseTrace ?? false;
+ final bool? _verboseTrace;
+
+ /// Whether to chain stack traces.
+ bool get chainStackTraces => _chainStackTraces ?? _verboseTrace ?? false;
+ final bool? _chainStackTraces;
+
+ /// The user-defined tags attached to the test or suite.
+ final Set<String> tags;
+
+ /// The number of times to re-run a test before being marked as a failure.
+ int get retry => _retry ?? 0;
+ final int? _retry;
+
+ /// Platform-specific metadata.
+ ///
+ /// Each key identifies a platform, and its value identifies the specific
+ /// metadata for that platform. These can be applied by calling [forPlatform].
+ final Map<PlatformSelector, Metadata> onPlatform;
+
+ /// Metadata that applies only when specific tags are applied.
+ ///
+ /// Tag-specific metadata is applied when merging this with other metadata.
+ /// Note that unlike [onPlatform], the base metadata takes precedence over any
+ /// tag-specific metadata.
+ ///
+ /// This is guaranteed not to have any keys that match [tags]; those are
+ /// resolved when the metadata is constructed.
+ final Map<BooleanSelector, Metadata> forTag;
+
+ /// The language version comment, if one is present.
+ ///
+ /// Only available for test suites and not individual tests.
+ final String? languageVersionComment;
+
+ /// Parses a user-provided map into the value for [onPlatform].
+ static Map<PlatformSelector, Metadata> _parseOnPlatform(
+ Map<String, dynamic>? onPlatform) {
+ if (onPlatform == null) return {};
+
+ var result = <PlatformSelector, Metadata>{};
+ onPlatform.forEach((platform, metadata) {
+ var selector = PlatformSelector.parse(platform);
+ if (metadata is Timeout || metadata is Skip) {
+ result[selector] = _parsePlatformOptions(platform, [metadata]);
+ } else if (metadata is List) {
+ result[selector] = _parsePlatformOptions(platform, metadata);
+ } else {
+ throw ArgumentError('Metadata for platform "$platform" must be a '
+ 'Timeout, Skip, or List of those; was "$metadata".');
+ }
+ });
+ return result;
+ }
+
+ static Metadata _parsePlatformOptions(
+ String platform, List<dynamic> metadata) {
+ Timeout? timeout;
+ dynamic skip;
+ for (var metadatum in metadata) {
+ if (metadatum is Timeout) {
+ if (timeout != null) {
+ throw ArgumentError('Only a single Timeout may be declared for '
+ '"$platform".');
+ }
+
+ timeout = metadatum;
+ } else if (metadatum is Skip) {
+ if (skip != null) {
+ throw ArgumentError('Only a single Skip may be declared for '
+ '"$platform".');
+ }
+
+ skip = metadatum.reason ?? true;
+ } else {
+ throw ArgumentError('Metadata for platform "$platform" must be a '
+ 'Timeout, Skip, or List of those; was "$metadata".');
+ }
+ }
+
+ return Metadata.parse(timeout: timeout, skip: skip);
+ }
+
+ /// Parses a user-provided [String] or [Iterable] into the value for [tags].
+ ///
+ /// Throws an [ArgumentError] if [tags] is not a [String] or an [Iterable].
+ static Set<String> _parseTags(Object? tags) {
+ if (tags == null) return {};
+ if (tags is String) return {tags};
+ if (tags is! Iterable) {
+ throw ArgumentError.value(
+ tags, 'tags', 'must be either a String or an Iterable.');
+ }
+
+ if (tags.any((tag) => tag is! String)) {
+ throw ArgumentError.value(tags, 'tags', 'must contain only Strings.');
+ }
+
+ return Set.from(tags);
+ }
+
+ /// Creates new Metadata.
+ ///
+ /// [testOn] defaults to [PlatformSelector.all].
+ ///
+ /// If [forTag] contains metadata that applies to [tags], that metadata is
+ /// included inline in the returned value. The values directly passed to the
+ /// constructor take precedence over tag-specific metadata.
+ factory Metadata(
+ {PlatformSelector? testOn,
+ Timeout? timeout,
+ bool? skip,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ int? retry,
+ String? skipReason,
+ Iterable<String>? tags,
+ Map<PlatformSelector, Metadata>? onPlatform,
+ Map<BooleanSelector, Metadata>? forTag,
+ String? languageVersionComment}) {
+ // Returns metadata without forTag resolved at all.
+ Metadata unresolved() => Metadata._(
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ retry: retry,
+ skipReason: skipReason,
+ tags: tags,
+ onPlatform: onPlatform,
+ forTag: forTag,
+ languageVersionComment: languageVersionComment);
+
+ // If there's no tag-specific metadata, or if none of it applies, just
+ // return the metadata as-is.
+ if (forTag == null || tags == null) return unresolved();
+ tags = Set.from(tags);
+ forTag = Map.from(forTag);
+
+ // Otherwise, resolve the tag-specific components. Doing this eagerly means
+ // we only have to resolve suite- or group-level tags once, rather than
+ // doing it for every test individually.
+ var empty = Metadata._();
+ var merged = forTag.keys.toList().fold(empty, (Metadata merged, selector) {
+ if (!selector.evaluate(tags!.contains)) return merged;
+ return merged.merge(forTag!.remove(selector)!);
+ });
+
+ if (merged == empty) return unresolved();
+ return merged.merge(unresolved());
+ }
+
+ /// Creates new Metadata.
+ ///
+ /// Unlike [Metadata], this assumes [forTag] is already resolved.
+ Metadata._({
+ PlatformSelector? testOn,
+ Timeout? timeout,
+ bool? skip,
+ this.skipReason,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ int? retry,
+ Iterable<String>? tags,
+ Map<PlatformSelector, Metadata>? onPlatform,
+ Map<BooleanSelector, Metadata>? forTag,
+ this.languageVersionComment,
+ }) : testOn = testOn ?? PlatformSelector.all,
+ timeout = timeout ?? const Timeout.factor(1),
+ _skip = skip,
+ _verboseTrace = verboseTrace,
+ _chainStackTraces = chainStackTraces,
+ _retry = retry,
+ tags = UnmodifiableSetView(tags == null ? {} : tags.toSet()),
+ onPlatform =
+ onPlatform == null ? const {} : UnmodifiableMapView(onPlatform),
+ forTag = forTag == null ? const {} : UnmodifiableMapView(forTag) {
+ if (retry != null) RangeError.checkNotNegative(retry, 'retry');
+ _validateTags();
+ }
+
+ /// Creates a new Metadata, but with fields parsed from caller-friendly values
+ /// where applicable.
+ ///
+ /// Throws a [FormatException] if any field is invalid.
+ Metadata.parse(
+ {String? testOn,
+ Timeout? timeout,
+ dynamic skip,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ int? retry,
+ Map<String, dynamic>? onPlatform,
+ Object? /* String|Iterable<String> */ tags,
+ this.languageVersionComment})
+ : testOn = testOn == null
+ ? PlatformSelector.all
+ : PlatformSelector.parse(testOn),
+ timeout = timeout ?? const Timeout.factor(1),
+ _skip = skip == null ? null : skip != false,
+ _verboseTrace = verboseTrace,
+ _chainStackTraces = chainStackTraces,
+ _retry = retry,
+ skipReason = skip is String ? skip : null,
+ onPlatform = _parseOnPlatform(onPlatform),
+ tags = _parseTags(tags),
+ forTag = const {} {
+ if (skip != null && skip is! String && skip is! bool) {
+ throw ArgumentError('"skip" must be a String or a bool, was "$skip".');
+ }
+
+ if (retry != null) RangeError.checkNotNegative(retry, 'retry');
+
+ _validateTags();
+ }
+
+ /// Deserializes the result of [Metadata.serialize] into a new [Metadata].
+ Metadata.deserialize(Map serialized)
+ : testOn = serialized['testOn'] == null
+ ? PlatformSelector.all
+ : PlatformSelector.parse(serialized['testOn'] as String),
+ timeout = _deserializeTimeout(serialized['timeout']),
+ _skip = serialized['skip'] as bool?,
+ skipReason = serialized['skipReason'] as String?,
+ _verboseTrace = serialized['verboseTrace'] as bool?,
+ _chainStackTraces = serialized['chainStackTraces'] as bool?,
+ _retry = (serialized['retry'] as num?)?.toInt(),
+ tags = Set.from(serialized['tags'] as Iterable),
+ onPlatform = {
+ for (var pair in serialized['onPlatform'] as List)
+ PlatformSelector.parse(pair.first as String):
+ Metadata.deserialize(pair.last as Map)
+ },
+ forTag = (serialized['forTag'] as Map).map((key, nested) => MapEntry(
+ BooleanSelector.parse(key as String),
+ Metadata.deserialize(nested as Map))),
+ languageVersionComment =
+ serialized['languageVersionComment'] as String?;
+
+ /// Deserializes timeout from the format returned by [_serializeTimeout].
+ static Timeout _deserializeTimeout(Object? serialized) {
+ if (serialized == 'none') return Timeout.none;
+ var scaleFactor = (serialized as Map)['scaleFactor'];
+ if (scaleFactor != null) return Timeout.factor(scaleFactor as num);
+ return Timeout(
+ Duration(microseconds: (serialized['duration'] as num).toInt()));
+ }
+
+ /// Throws an [ArgumentError] if any tags in [tags] aren't hyphenated
+ /// identifiers.
+ void _validateTags() {
+ var invalidTags = tags
+ .where((tag) => !tag.contains(anchoredHyphenatedIdentifier))
+ .map((tag) => '"$tag"')
+ .toList();
+
+ if (invalidTags.isEmpty) return;
+
+ throw ArgumentError("Invalid ${pluralize('tag', invalidTags.length)} "
+ '${toSentence(invalidTags)}. Tags must be (optionally hyphenated) '
+ 'Dart identifiers.');
+ }
+
+ /// Throws a [FormatException] if any [PlatformSelector]s use any variables
+ /// that don't appear either in [validVariables] or in the set of variables
+ /// that are known to be valid for all selectors.
+ void validatePlatformSelectors(Set<String> validVariables) {
+ testOn.validate(validVariables);
+ onPlatform.forEach((selector, metadata) {
+ selector.validate(validVariables);
+ metadata.validatePlatformSelectors(validVariables);
+ });
+ }
+
+ /// Return a new [Metadata] that merges [this] with [other].
+ ///
+ /// If the two [Metadata]s have conflicting properties, [other] wins. If
+ /// either has a [forTag] metadata for one of the other's tags, that metadata
+ /// is merged as well.
+ Metadata merge(Metadata other) => Metadata(
+ testOn: testOn.intersection(other.testOn),
+ timeout: timeout.merge(other.timeout),
+ skip: other._skip ?? _skip,
+ skipReason: other.skipReason ?? skipReason,
+ verboseTrace: other._verboseTrace ?? _verboseTrace,
+ chainStackTraces: other._chainStackTraces ?? _chainStackTraces,
+ retry: other._retry ?? _retry,
+ tags: tags.union(other.tags),
+ onPlatform: mergeMaps(onPlatform, other.onPlatform,
+ value: (metadata1, metadata2) => metadata1.merge(metadata2)),
+ forTag: mergeMaps(forTag, other.forTag,
+ value: (metadata1, metadata2) => metadata1.merge(metadata2)),
+ languageVersionComment:
+ other.languageVersionComment ?? languageVersionComment);
+
+ /// Returns a copy of [this] with the given fields changed.
+ Metadata change(
+ {PlatformSelector? testOn,
+ Timeout? timeout,
+ bool? skip,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ int? retry,
+ String? skipReason,
+ Map<PlatformSelector, Metadata>? onPlatform,
+ Set<String>? tags,
+ Map<BooleanSelector, Metadata>? forTag,
+ String? languageVersionComment}) {
+ testOn ??= this.testOn;
+ timeout ??= this.timeout;
+ skip ??= _skip;
+ verboseTrace ??= _verboseTrace;
+ chainStackTraces ??= _chainStackTraces;
+ retry ??= _retry;
+ skipReason ??= this.skipReason;
+ onPlatform ??= this.onPlatform;
+ tags ??= this.tags;
+ forTag ??= this.forTag;
+ languageVersionComment ??= this.languageVersionComment;
+ return Metadata(
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skipReason: skipReason,
+ onPlatform: onPlatform,
+ tags: tags,
+ forTag: forTag,
+ retry: retry,
+ languageVersionComment: languageVersionComment);
+ }
+
+ /// Returns a copy of [this] with all platform-specific metadata from
+ /// [onPlatform] resolved.
+ Metadata forPlatform(SuitePlatform platform) {
+ if (onPlatform.isEmpty) return this;
+
+ var metadata = this;
+ onPlatform.forEach((platformSelector, platformMetadata) {
+ if (!platformSelector.evaluate(platform)) return;
+ metadata = metadata.merge(platformMetadata);
+ });
+ return metadata.change(onPlatform: {});
+ }
+
+ /// Serializes [this] into a JSON-safe object that can be deserialized using
+ /// [Metadata.deserialize].
+ Map<String, dynamic> serialize() {
+ // Make this a list to guarantee that the order is preserved.
+ var serializedOnPlatform = <List<Object>>[];
+ onPlatform.forEach((key, value) {
+ serializedOnPlatform.add([key.toString(), value.serialize()]);
+ });
+
+ return {
+ 'testOn': testOn == PlatformSelector.all ? null : testOn.toString(),
+ 'timeout': _serializeTimeout(timeout),
+ 'skip': _skip,
+ 'skipReason': skipReason,
+ 'verboseTrace': _verboseTrace,
+ 'chainStackTraces': _chainStackTraces,
+ 'retry': _retry,
+ 'tags': tags.toList(),
+ 'onPlatform': serializedOnPlatform,
+ 'forTag': forTag.map((selector, metadata) =>
+ MapEntry(selector.toString(), metadata.serialize())),
+ 'languageVersionComment': languageVersionComment,
+ };
+ }
+
+ /// Serializes timeout into a JSON-safe object.
+ dynamic _serializeTimeout(Timeout timeout) {
+ if (timeout == Timeout.none) return 'none';
+ return {
+ 'duration': timeout.duration?.inMicroseconds,
+ 'scaleFactor': timeout.scaleFactor
+ };
+ }
+}
diff --git a/pkgs/test_api/lib/src/backend/operating_system.dart b/pkgs/test_api/lib/src/backend/operating_system.dart
new file mode 100644
index 0000000..a86c1e7
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/operating_system.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2015, 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.
+
+/// An enum of all operating systems supported by Dart.
+///
+/// This is used for selecting which operating systems a test can run on. Even
+/// for browser tests, this indicates the operating system of the machine
+/// running the test runner.
+class OperatingSystem {
+ /// Microsoft Windows.
+ static const windows = OperatingSystem._('Windows', 'windows');
+
+ /// Mac OS X.
+ static const macOS = OperatingSystem._('OS X', 'mac-os');
+
+ /// GNU/Linux.
+ static const linux = OperatingSystem._('Linux', 'linux');
+
+ /// Android.
+ ///
+ /// Since this is the operating system the test runner is running on, this
+ /// won't be true when testing remotely on an Android browser.
+ static const android = OperatingSystem._('Android', 'android');
+
+ /// iOS.
+ ///
+ /// Since this is the operating system the test runner is running on, this
+ /// won't be true when testing remotely on an iOS browser.
+ static const iOS = OperatingSystem._('iOS', 'ios');
+
+ /// No operating system.
+ ///
+ /// This is used when running in the browser, or if an unrecognized operating
+ /// system is used. It can't be referenced by name in platform selectors.
+ static const none = OperatingSystem._('none', 'none');
+
+ /// A list of all instances of [OperatingSystem] other than [none].
+ static const all = [windows, macOS, linux, android, iOS];
+
+ /// Finds an operating system by its name.
+ ///
+ /// If no operating system is found, returns [none].
+ static OperatingSystem find(String identifier) =>
+ all.firstWhere((platform) => platform.identifier == identifier,
+ orElse: () => none);
+
+ /// Finds an operating system by the return value from `dart:io`'s
+ /// `Platform.operatingSystem`.
+ ///
+ /// If no operating system is found, returns [none].
+ static OperatingSystem findByIoName(String name) => switch (name) {
+ 'windows' => windows,
+ 'macos' => macOS,
+ 'linux' => linux,
+ 'android' => android,
+ 'ios' => iOS,
+ _ => none,
+ };
+
+ /// The human-friendly of the operating system.
+ final String name;
+
+ /// The identifier used to look up the operating system.
+ final String identifier;
+
+ /// Whether this is a POSIX-ish operating system.
+ bool get isPosix => this != windows && this != none;
+
+ const OperatingSystem._(this.name, this.identifier);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/test_api/lib/src/backend/platform_selector.dart b/pkgs/test_api/lib/src/backend/platform_selector.dart
new file mode 100644
index 0000000..c65dc8b
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/platform_selector.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2015, 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 'package:boolean_selector/boolean_selector.dart';
+import 'package:source_span/source_span.dart';
+
+import 'compiler.dart';
+import 'operating_system.dart';
+import 'runtime.dart';
+import 'suite_platform.dart';
+
+/// The set of variable names that are valid for all platform selectors.
+final _universalValidVariables = {
+ 'posix',
+ 'dart-vm',
+ 'browser',
+ 'js',
+ 'blink',
+ 'google',
+ 'wasm',
+ for (var runtime in Runtime.builtIn) runtime.identifier,
+ for (var compiler in Compiler.builtIn) compiler.identifier,
+ for (var os in OperatingSystem.all) os.identifier,
+};
+
+/// An expression for selecting certain platforms, including operating systems
+/// and browsers.
+///
+/// This uses the [boolean selector][] syntax.
+///
+/// [boolean selector]: https://pub.dev/packages/boolean_selector
+final class PlatformSelector {
+ /// A selector that declares that a test can be run on all platforms.
+ static const all = PlatformSelector._(BooleanSelector.all);
+
+ /// The boolean selector used to implement this selector.
+ final BooleanSelector _inner;
+
+ /// The source span from which this selector was parsed.
+ final SourceSpan? _span;
+
+ /// Parses [selector].
+ ///
+ /// If [span] is passed, it indicates the location of the text for [selector]
+ /// in a larger document. It's used for error reporting.
+ PlatformSelector.parse(String selector, [SourceSpan? span])
+ : _inner =
+ _wrapFormatException(() => BooleanSelector.parse(selector), span),
+ _span = span;
+
+ const PlatformSelector._(this._inner) : _span = null;
+
+ /// Runs [body] and wraps any [FormatException] it throws in a
+ /// [SourceSpanFormatException] using [span].
+ ///
+ /// If [span] is `null`, runs [body] as-is.
+ static T _wrapFormatException<T>(T Function() body, [SourceSpan? span]) {
+ if (span == null) return body();
+
+ try {
+ return body();
+ } on FormatException catch (error) {
+ throw SourceSpanFormatException(error.message, span);
+ }
+ }
+
+ /// Throws a [FormatException] if this selector uses any variables that don't
+ /// appear either in [validVariables] or in the set of variables that are
+ /// known to be valid for all selectors.
+ void validate(Set<String> validVariables) {
+ if (identical(this, all)) return;
+
+ _wrapFormatException(
+ () => _inner.validate((name) =>
+ _universalValidVariables.contains(name) ||
+ validVariables.contains(name)),
+ _span);
+ }
+
+ /// Returns whether the selector matches the given [platform].
+ bool evaluate(SuitePlatform platform) =>
+ _inner.evaluate((String variable) => switch (variable) {
+ _
+ when variable == platform.runtime.identifier ||
+ variable == platform.runtime.parent?.identifier ||
+ variable == platform.os.identifier ||
+ variable == platform.compiler.identifier =>
+ true,
+ 'dart-vm' => platform.runtime.isDartVM,
+ 'browser' => platform.runtime.isBrowser,
+ 'js' => platform.compiler.isJS,
+ 'blink' => platform.runtime.isBlink,
+ 'posix' => platform.os.isPosix,
+ 'google' => platform.inGoogle,
+ 'wasm' => platform.compiler.isWasm,
+ _ => false,
+ });
+
+ /// Returns a new [PlatformSelector] that matches only platforms matched by
+ /// both [this] and [other].
+ PlatformSelector intersection(PlatformSelector other) {
+ if (other == PlatformSelector.all) return this;
+ return PlatformSelector._(_inner.intersection(other._inner));
+ }
+
+ @override
+ String toString() => _inner.toString();
+
+ @override
+ bool operator ==(Object other) =>
+ other is PlatformSelector && _inner == other._inner;
+
+ @override
+ int get hashCode => _inner.hashCode;
+}
diff --git a/pkgs/test_api/lib/src/backend/remote_exception.dart b/pkgs/test_api/lib/src/backend/remote_exception.dart
new file mode 100644
index 0000000..0d70561
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/remote_exception.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+
+import 'test_failure.dart';
+
+/// An exception that was thrown remotely.
+///
+/// This could be an exception thrown in a different isolate, a different
+/// process, or on an entirely different computer.
+final class RemoteException implements Exception {
+ /// The original exception's message, if it had one.
+ ///
+ /// If the original exception was a plain string, this will contain that
+ /// string.
+ final String? message;
+
+ /// The value of the original exception's `runtimeType.toString()`.
+ final String type;
+
+ /// The value of the original exception's `toString()`.
+ final String _toString;
+
+ /// Serializes [error] and [stackTrace] into a JSON-safe object.
+ ///
+ /// Other than JSON- and isolate-safety, no guarantees are made about the
+ /// serialized format.
+ static Map<String, dynamic> serialize(dynamic error, StackTrace stackTrace) {
+ String? message;
+ if (error is String) {
+ message = error;
+ } else {
+ try {
+ message = error.message.toString();
+ } on NoSuchMethodError catch (_) {
+ // Do nothing.
+ }
+ }
+
+ final supertype = (error is TestFailure) ? 'TestFailure' : null;
+
+ return {
+ 'message': message,
+ 'type': error.runtimeType.toString(),
+ 'supertype': supertype,
+ 'toString': error.toString(),
+ 'stackChain': Chain.forTrace(stackTrace).toString()
+ };
+ }
+
+ /// Deserializes an exception serialized with [RemoteException.serialize].
+ ///
+ /// The returned [AsyncError] is guaranteed to have a [RemoteException] as its
+ /// error and a [Chain] as its stack trace.
+ static AsyncError deserialize(Map serialized) {
+ return AsyncError(_deserializeException(serialized),
+ Chain.parse(serialized['stackChain'] as String));
+ }
+
+ /// Deserializes the exception portion of [serialized].
+ static RemoteException _deserializeException(Map serialized) {
+ final message = serialized['message'] as String?;
+ final type = serialized['type'] as String;
+ final toString = serialized['toString'] as String;
+
+ return switch (serialized['supertype'] as String?) {
+ 'TestFailure' => _RemoteTestFailure(message, type, toString),
+ _ => RemoteException._(message, type, toString),
+ };
+ }
+
+ RemoteException._(this.message, this.type, this._toString);
+
+ @override
+ String toString() => _toString;
+}
+
+/// A subclass of [RemoteException] that implements [TestFailure].
+///
+/// It's important to preserve [TestFailure]-ness, because tests have different
+/// results depending on whether an exception was a failure or an error.
+final class _RemoteTestFailure extends RemoteException implements TestFailure {
+ _RemoteTestFailure(super.message, super.type, super.toString) : super._();
+}
diff --git a/pkgs/test_api/lib/src/backend/remote_listener.dart b/pkgs/test_api/lib/src/backend/remote_listener.dart
new file mode 100644
index 0000000..66a0814
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/remote_listener.dart
@@ -0,0 +1,276 @@
+// Copyright (c) 2016, 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 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:term_glyph/term_glyph.dart' as glyph;
+
+import 'declarer.dart';
+import 'group.dart';
+import 'invoker.dart';
+import 'live_test.dart';
+import 'metadata.dart';
+import 'remote_exception.dart';
+import 'stack_trace_formatter.dart';
+import 'suite.dart';
+import 'suite_channel_manager.dart';
+import 'suite_platform.dart';
+import 'test.dart';
+
+final class RemoteListener {
+ /// The test suite to run.
+ final Suite _suite;
+
+ /// The zone to forward prints to, or `null` if prints shouldn't be forwarded.
+ final Zone? _printZone;
+
+ /// Extracts metadata about all the tests in the function returned by
+ /// [getMain] and returns a channel that will send information about them.
+ ///
+ /// The main function is wrapped in a closure so that we can handle it being
+ /// undefined here rather than in the generated code.
+ ///
+ /// Once that's done, this starts listening for commands about which tests to
+ /// run.
+ ///
+ /// If [hidePrints] is `true` (the default), calls to `print()` within this
+ /// suite will not be forwarded to the parent zone's print handler. However,
+ /// the caller may want them to be forwarded in (for example) a browser
+ /// context where they'll be visible in the development console.
+ ///
+ /// If [beforeLoad] is passed, it's called before the tests have been declared
+ /// for this worker.
+ static StreamChannel<Object?> start(Function Function() getMain,
+ {bool hidePrints = true,
+ Future Function(
+ StreamChannel<Object?> Function(String name) suiteChannel)?
+ beforeLoad}) {
+ // Synchronous in order to allow `print` output to show up immediately, even
+ // if they are followed by long running synchronous work.
+ var controller =
+ StreamChannelController<Object?>(allowForeignErrors: false, sync: true);
+ var channel = MultiChannel<Object?>(controller.local);
+
+ var verboseChain = true;
+
+ var printZone = hidePrints ? null : Zone.current;
+ var spec = ZoneSpecification(print: (_, __, ___, line) {
+ if (printZone != null) printZone.print(line);
+ channel.sink.add({'type': 'print', 'line': line});
+ });
+
+ final suiteChannelManager = SuiteChannelManager();
+ StackTraceFormatter().asCurrent(() {
+ runZonedGuarded(() async {
+ Function? main;
+ try {
+ main = getMain();
+ } on NoSuchMethodError catch (_) {
+ _sendLoadException(channel, 'No top-level main() function defined.');
+ return;
+ } catch (error, stackTrace) {
+ _sendError(channel, error, stackTrace, verboseChain);
+ return;
+ }
+
+ if (main is! FutureOr<void> Function()) {
+ _sendLoadException(
+ channel, 'Top-level main() function takes arguments.');
+ return;
+ }
+
+ var queue = StreamQueue(channel.stream);
+ var message = await queue.next as Map;
+ assert(message['type'] == 'initial');
+
+ queue.rest.cast<Map>().listen((message) {
+ if (message['type'] == 'close') {
+ controller.local.sink.close();
+ return;
+ }
+
+ assert(message['type'] == 'suiteChannel');
+ suiteChannelManager.connectIn(message['name'] as String,
+ channel.virtualChannel((message['id'] as num).toInt()));
+ });
+
+ if ((message['asciiGlyphs'] as bool?) ?? false) glyph.ascii = true;
+ var metadata = Metadata.deserialize(message['metadata'] as Map);
+ verboseChain = metadata.verboseTrace;
+ var declarer = Declarer(
+ metadata: metadata,
+ platformVariables: Set.from(message['platformVariables'] as Iterable),
+ collectTraces: message['collectTraces'] as bool,
+ noRetry: message['noRetry'] as bool,
+ // TODO: Change to non-nullable https://github.com/dart-lang/test/issues/1591
+ allowDuplicateTestNames:
+ message['allowDuplicateTestNames'] as bool? ?? true,
+ );
+ StackTraceFormatter.current!.configure(
+ except: _deserializeSet(message['foldTraceExcept'] as List),
+ only: _deserializeSet(message['foldTraceOnly'] as List));
+
+ if (beforeLoad != null) {
+ await beforeLoad(suiteChannelManager.connectOut);
+ }
+
+ await declarer.declare(main);
+
+ var suite = Suite(
+ declarer.build(),
+ SuitePlatform.deserialize(message['platform'] as Object),
+ path: message['path'] as String,
+ ignoreTimeouts: message['ignoreTimeouts'] as bool? ?? false,
+ );
+
+ runZoned(() {
+ Invoker.guard(
+ () => RemoteListener._(suite, printZone)._listen(channel));
+ },
+ // Make the declarer visible to running tests so that they'll throw
+ // useful errors when calling `test()` and `group()` within a test,
+ // and so they can add to the declarer's `tearDownAll()` list.
+ zoneValues: {#test.declarer: declarer});
+ }, (error, stackTrace) {
+ _sendError(channel, error, stackTrace, verboseChain);
+ }, zoneSpecification: spec);
+ });
+
+ return controller.foreign;
+ }
+
+ /// Returns a [Set] from a JSON serialized list of strings, or `null` if the
+ /// list is empty or `null`.
+ static Set<String>? _deserializeSet(List? list) {
+ if (list == null) return null;
+ if (list.isEmpty) return null;
+ return Set.from(list);
+ }
+
+ /// Sends a message over [channel] indicating that the tests failed to load.
+ ///
+ /// [message] should describe the failure.
+ static void _sendLoadException(StreamChannel channel, String message) {
+ channel.sink.add({'type': 'loadException', 'message': message});
+ }
+
+ /// Sends a message over [channel] indicating an error from user code.
+ static void _sendError(StreamChannel channel, Object error,
+ StackTrace stackTrace, bool verboseChain) {
+ channel.sink.add({
+ 'type': 'error',
+ 'error': RemoteException.serialize(
+ error,
+ StackTraceFormatter.current!
+ .formatStackTrace(stackTrace, verbose: verboseChain))
+ });
+ }
+
+ RemoteListener._(this._suite, this._printZone);
+
+ /// Send information about [_suite] across [channel] and start listening for
+ /// commands to run the tests.
+ void _listen(MultiChannel channel) {
+ channel.sink.add({
+ 'type': 'success',
+ 'root': _serializeGroup(channel, _suite.group, [])
+ });
+ }
+
+ /// Serializes [group] into a JSON-safe map.
+ ///
+ /// [parents] lists the groups that contain [group].
+ Map _serializeGroup(
+ MultiChannel channel, Group group, Iterable<Group> parents) {
+ parents = parents.toList()..add(group);
+ return {
+ 'type': 'group',
+ 'name': group.name,
+ 'metadata': group.metadata.serialize(),
+ 'trace': group.trace == null
+ ? null
+ : StackTraceFormatter.current
+ ?.formatStackTrace(group.trace!)
+ .toString() ??
+ group.trace?.toString(),
+ 'setUpAll': _serializeTest(channel, group.setUpAll, parents),
+ 'tearDownAll': _serializeTest(channel, group.tearDownAll, parents),
+ 'entries': group.entries.map((entry) {
+ return entry is Group
+ ? _serializeGroup(channel, entry, parents)
+ : _serializeTest(channel, entry as Test, parents);
+ }).toList()
+ };
+ }
+
+ /// Serializes [test] into a JSON-safe map.
+ ///
+ /// [groups] lists the groups that contain [test]. Returns `null` if [test]
+ /// is `null`.
+ Map? _serializeTest(
+ MultiChannel channel, Test? test, Iterable<Group>? groups) {
+ if (test == null) return null;
+
+ var testChannel = channel.virtualChannel();
+ testChannel.stream.listen((message) {
+ assert(message['command'] == 'run');
+ _runLiveTest(test.load(_suite, groups: groups),
+ channel.virtualChannel((message['channel'] as num).toInt()));
+ });
+
+ return {
+ 'type': 'test',
+ 'name': test.name,
+ 'metadata': test.metadata.serialize(),
+ 'trace': test.trace == null
+ ? null
+ : StackTraceFormatter.current
+ ?.formatStackTrace(test.trace!)
+ .toString() ??
+ test.trace?.toString(),
+ 'channel': testChannel.id
+ };
+ }
+
+ /// Runs [liveTest] and sends the results across [channel].
+ void _runLiveTest(LiveTest liveTest, MultiChannel channel) {
+ channel.stream.listen((message) {
+ assert(message['command'] == 'close');
+ liveTest.close();
+ });
+
+ liveTest.onStateChange.listen((state) {
+ channel.sink.add({
+ 'type': 'state-change',
+ 'status': state.status.name,
+ 'result': state.result.name
+ });
+ });
+
+ liveTest.onError.listen((asyncError) {
+ channel.sink.add({
+ 'type': 'error',
+ 'error': RemoteException.serialize(
+ asyncError.error,
+ StackTraceFormatter.current!.formatStackTrace(asyncError.stackTrace,
+ verbose: liveTest.test.metadata.verboseTrace))
+ });
+ });
+
+ liveTest.onMessage.listen((message) {
+ if (_printZone != null) _printZone.print(message.text);
+ channel.sink.add({
+ 'type': 'message',
+ 'message-type': message.type.name,
+ 'text': message.text
+ });
+ });
+
+ runZoned(() {
+ liveTest.run().then((_) => channel.sink.add({'type': 'complete'}));
+ }, zoneValues: {#test.runner.test_channel: channel});
+ }
+}
diff --git a/pkgs/test_api/lib/src/backend/runtime.dart b/pkgs/test_api/lib/src/backend/runtime.dart
new file mode 100644
index 0000000..ceef227
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/runtime.dart
@@ -0,0 +1,193 @@
+// Copyright (c) 2015, 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 'compiler.dart';
+
+/// An enum of all Dart runtimes supported by the test runner.
+final class Runtime {
+ // When adding new runtimes, be sure to update the baseline and derived
+ // variable tests in test/backend/platform_selector/evaluate_test.
+
+ /// The command-line Dart VM.
+ static const Runtime vm = Runtime('VM', 'vm', Compiler.kernel,
+ [Compiler.kernel, Compiler.source, Compiler.exe],
+ isDartVM: true);
+
+ /// Google Chrome.
+ static const Runtime chrome = Runtime('Chrome', 'chrome', Compiler.dart2js,
+ [Compiler.dart2js, Compiler.dart2wasm],
+ isBrowser: true, isBlink: true);
+
+ /// Mozilla Firefox.
+ static const Runtime firefox = Runtime('Firefox', 'firefox', Compiler.dart2js,
+ [Compiler.dart2js, Compiler.dart2wasm],
+ isBrowser: true);
+
+ /// Apple Safari.
+ static const Runtime safari = Runtime(
+ 'Safari', 'safari', Compiler.dart2js, [Compiler.dart2js],
+ isBrowser: true);
+
+ /// Microsoft Internet Explorer.
+ @Deprecated('Internet Explorer is no longer supported')
+ static const Runtime internetExplorer = Runtime(
+ 'Internet Explorer', 'ie', Compiler.dart2js, [Compiler.dart2js],
+ isBrowser: true);
+
+ /// Microsoft Edge (based on Chromium).
+ static const Runtime edge = Runtime(
+ 'Microsoft Edge', 'edge', Compiler.dart2js, [Compiler.dart2js],
+ isBrowser: true, isBlink: true);
+
+ /// The command-line Node.js VM.
+ static const Runtime nodeJS = Runtime('Node.js', 'node', Compiler.dart2js,
+ [Compiler.dart2js, Compiler.dart2wasm]);
+
+ /// The platforms that are supported by the test runner by default.
+ static const List<Runtime> builtIn = [
+ Runtime.vm,
+ Runtime.chrome,
+ Runtime.firefox,
+ Runtime.safari,
+ Runtime.edge,
+ Runtime.nodeJS,
+ ];
+
+ /// The human-friendly name of the platform.
+ final String name;
+
+ /// The identifier used to look up the platform.
+ final String identifier;
+
+ /// The parent platform that this is based on, or `null` if there is no
+ /// parent.
+ final Runtime? parent;
+
+ /// Returns whether this is a child of another platform.
+ bool get isChild => parent != null;
+
+ /// Whether this platform runs the Dart VM in any capacity.
+ final bool isDartVM;
+
+ /// Whether this platform is a browser.
+ final bool isBrowser;
+
+ /// Whether this platform uses the Blink rendering engine.
+ final bool isBlink;
+
+ /// Whether this platform has no visible window.
+ final bool isHeadless;
+
+ /// Returns the platform this is based on, or [this] if it's not based on
+ /// anything.
+ ///
+ /// That is, returns [parent] if it's non-`null` or [this] if it's `null`.
+ Runtime get root => parent ?? this;
+
+ /// The default compiler to use with this runtime.
+ final Compiler defaultCompiler;
+
+ /// All the supported compilers for this runtime.
+ final List<Compiler> supportedCompilers;
+
+ const Runtime(
+ this.name, this.identifier, this.defaultCompiler, this.supportedCompilers,
+ {this.isDartVM = false,
+ this.isBrowser = false,
+ this.isBlink = false,
+ this.isHeadless = false})
+ : parent = null;
+
+ Runtime._child(this.name, this.identifier, this.defaultCompiler,
+ this.supportedCompilers, Runtime this.parent)
+ : isDartVM = parent.isDartVM,
+ isBrowser = parent.isBrowser,
+ isBlink = parent.isBlink,
+ isHeadless = parent.isHeadless;
+
+ /// Converts a JSON-safe representation generated by [serialize] back into a
+ /// [Runtime].
+ factory Runtime.deserialize(Object serialized) {
+ if (serialized is String) {
+ return builtIn
+ .firstWhere((platform) => platform.identifier == serialized);
+ }
+
+ var map = serialized as Map;
+ var name = map['name'] as String;
+ var identifier = map['identifier'] as String;
+ var defaultCompiler =
+ Compiler.deserialize(map['defaultCompiler'] as Object);
+ var supportedCompilers = [
+ for (var compiler in map['supportedCompilers'] as List)
+ Compiler.deserialize(compiler as Object),
+ ];
+
+ var parent = map['parent'];
+ if (parent != null) {
+ // Note that the returned platform's [parent] won't necessarily be `==` to
+ // a separately-deserialized parent platform. This should be fine, though,
+ // since we only deserialize platforms in the remote execution context
+ // where they're only used to evaluate platform selectors.
+ return Runtime._child(name, identifier, defaultCompiler,
+ supportedCompilers, Runtime.deserialize(parent as Object));
+ }
+
+ return Runtime(name, identifier, defaultCompiler, supportedCompilers,
+ isDartVM: map['isDartVM'] as bool,
+ isBrowser: map['isBrowser'] as bool,
+ isBlink: map['isBlink'] as bool,
+ isHeadless: map['isHeadless'] as bool);
+ }
+
+ /// Converts [this] into a JSON-safe object that can be converted back to a
+ /// [Runtime] using [Runtime.deserialize].
+ Object serialize() {
+ if (builtIn.contains(this)) return identifier;
+
+ if (parent != null) {
+ return {
+ 'name': name,
+ 'defaultCompiler': defaultCompiler.serialize(),
+ 'supportedCompilers': [
+ for (var compiler in supportedCompilers) compiler.serialize(),
+ ],
+ 'identifier': identifier,
+ 'parent': parent!.serialize()
+ };
+ }
+
+ return {
+ 'name': name,
+ 'defaultCompiler': defaultCompiler.serialize(),
+ 'supportedCompilers': [
+ for (var compiler in supportedCompilers) compiler.serialize(),
+ ],
+ 'identifier': identifier,
+ 'isDartVM': isDartVM,
+ 'isBrowser': isBrowser,
+ 'isBlink': isBlink,
+ 'isHeadless': isHeadless,
+ // TODO(https://github.com/dart-lang/test/issues/2146): Remove this.
+ 'isJS': isBrowser || this == Runtime.nodeJS,
+ // TODO(https://github.com/dart-lang/test/issues/2146): Remove this.
+ 'isWasm': false,
+ };
+ }
+
+ /// Returns a child of [this] that counts as both this platform's identifier
+ /// and the new [identifier].
+ ///
+ /// This may not be called on a platform that's already a child.
+ Runtime extend(String name, String identifier) {
+ if (parent == null) {
+ return Runtime._child(
+ name, identifier, defaultCompiler, supportedCompilers, this);
+ }
+ throw StateError('A child platform may not be extended.');
+ }
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart b/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart
new file mode 100644
index 0000000..a495e89
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2018, 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 'package:stack_trace/stack_trace.dart';
+
+import 'invoker.dart';
+import 'stack_trace_mapper.dart';
+
+/// The key used to look up [StackTraceFormatter.current] in a zone.
+final _currentKey = Object();
+
+/// A class that tracks how to format a stack trace according to the user's
+/// configuration.
+///
+/// This can convert JavaScript stack traces to Dart using source maps, and fold
+/// irrelevant frames out of the stack trace.
+final class StackTraceFormatter {
+ /// A class that converts [trace] into a Dart stack trace, or `null` to use it
+ /// as-is.
+ StackTraceMapper? _mapper;
+
+ /// The set of packages to fold when producing terse [Chain]s.
+ var _except = {'matcher', 'stream_channel', 'test', 'test_api'};
+
+ /// If non-empty, all packages not in this list will be folded when producing
+ /// terse [Chain]s.
+ var _only = <String>{};
+
+ /// Returns the current manager, or `null` if this isn't called within a call
+ /// to [asCurrent].
+ static StackTraceFormatter? get current =>
+ Zone.current[_currentKey] as StackTraceFormatter?;
+
+ /// Runs [body] with this as [StackTraceFormatter.current].
+ ///
+ /// This is zone-scoped, so this will be the current configuration in any
+ /// asynchronous callbacks transitively created by [body].
+ T asCurrent<T>(T Function() body) =>
+ runZoned(body, zoneValues: {_currentKey: this});
+
+ /// Configure how stack traces are formatted.
+ ///
+ /// The [mapper] is used to convert JavaScript traces into Dart traces. The
+ /// [except] set indicates packages whose frames should be folded away. If
+ /// [only] is non-empty, it indicates packages whose frames should *not* be
+ /// folded away.
+ void configure(
+ {StackTraceMapper? mapper, Set<String>? except, Set<String>? only}) {
+ if (mapper != null) _mapper = mapper;
+ if (except != null) _except = except;
+ if (only != null) _only = only;
+ }
+
+ /// Converts [stackTrace] to a [Chain] and formats it according to the user's
+ /// preferences.
+ ///
+ /// If [verbose] is `true`, this doesn't fold out irrelevant stack frames. It
+ /// defaults to the current test's [Metadata.verboseTrace] configuration, or
+ /// `false` if there is no current test.
+ Chain formatStackTrace(StackTrace stackTrace, {bool? verbose}) {
+ verbose ??= Invoker.current?.liveTest.test.metadata.verboseTrace ?? false;
+
+ var chain =
+ Chain.forTrace(_mapper?.mapStackTrace(stackTrace) ?? stackTrace);
+ if (verbose) return chain;
+
+ return chain.foldFrames((frame) {
+ if (_only.isNotEmpty) return !_only.contains(frame.package);
+ return _except.contains(frame.package);
+ }, terse: true);
+ }
+}
diff --git a/pkgs/test_api/lib/src/backend/stack_trace_mapper.dart b/pkgs/test_api/lib/src/backend/stack_trace_mapper.dart
new file mode 100644
index 0000000..b091bbe
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/stack_trace_mapper.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2015, 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.
+
+/// A class for mapping JS stack traces to Dart stack traces using source maps.
+abstract interface class StackTraceMapper {
+ /// Converts [trace] into a Dart stack trace.
+ StackTrace mapStackTrace(StackTrace trace);
+
+ /// Returns a Map representation which is suitable for JSON serialization.
+ Map<String, dynamic> serialize();
+}
diff --git a/pkgs/test_api/lib/src/backend/state.dart b/pkgs/test_api/lib/src/backend/state.dart
new file mode 100644
index 0000000..5a37654
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/state.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2015, 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.
+
+/// The state of a [LiveTest].
+///
+/// A test's state is made up of two components, its [status] and its [result].
+/// The [status] represents where the test is in its process of running; the
+/// [result] represents the outcome as far as its known.
+class State {
+ /// Where the test is in its process of running.
+ final Status status;
+
+ /// The outcome of the test, as far as it's known.
+ ///
+ /// Note that if [status] is [Status.pending], [result] will always be
+ /// [Result.success] since the test hasn't yet had a chance to fail.
+ final Result result;
+
+ /// Whether a test in this state is expected to be done running code.
+ ///
+ /// If [status] is [Status.complete] and [result] doesn't indicate an error, a
+ /// properly-written test case should not be running any more code. However,
+ /// it may have started asynchronous processes without notifying the test
+ /// runner.
+ bool get shouldBeDone => status == Status.complete && result.isPassing;
+
+ const State(this.status, this.result);
+
+ @override
+ bool operator ==(Object other) =>
+ other is State && status == other.status && result == other.result;
+
+ @override
+ int get hashCode => status.hashCode ^ (7 * result.hashCode);
+
+ @override
+ String toString() {
+ if (status == Status.pending) return 'pending';
+ if (status == Status.complete) return result.toString();
+ if (result == Result.success) return 'running';
+ return 'running with $result';
+ }
+}
+
+/// Where the test is in its process of running.
+enum Status {
+ /// The test has not yet begun running.
+ pending,
+
+ /// The test is currently running.
+ running,
+
+ /// The test has finished running.
+ ///
+ /// Note that even if the test is marked [complete], it may still be running
+ /// code asynchronously. A test is considered complete either once it hits its
+ /// first error or when all [expectAsync] callbacks have been called and any
+ /// returned [Future] has completed, but it's possible for further processing
+ /// to happen, which may cause further errors.
+ complete;
+
+ factory Status.parse(String name) => Status.values.byName(name);
+
+ @override
+ String toString() => name;
+}
+
+/// The outcome of the test, as far as it's known.
+enum Result {
+ /// The test has not yet failed in any way.
+ ///
+ /// Note that this doesn't mean that the test won't fail in the future.
+ success,
+
+ /// The test, or some part of it, has been skipped.
+ ///
+ /// This implies that the test hasn't failed *yet*. However, it this doesn't
+ /// mean that the test won't fail in the future.
+ skipped,
+
+ /// The test has failed.
+ ///
+ /// A failure is specifically caused by a [TestFailure] being thrown; any
+ /// other exception causes an error.
+ failure,
+
+ /// The test has crashed.
+ ///
+ /// Any exception other than a [TestFailure] is considered to be an error.
+ error;
+
+ /// Whether this is a passing result.
+ ///
+ /// A test is considered to have passed if it's a success or if it was
+ /// skipped.
+ bool get isPassing => this == success || this == skipped;
+
+ /// Whether this is a failing result.
+ ///
+ /// A test is considered to have failed if it experiences a failure or an
+ /// error.
+ bool get isFailing => !isPassing;
+
+ factory Result.parse(String name) => Result.values.byName(name);
+
+ @override
+ String toString() => name;
+}
diff --git a/pkgs/test_api/lib/src/backend/suite.dart b/pkgs/test_api/lib/src/backend/suite.dart
new file mode 100644
index 0000000..910f184
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/suite.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2015, 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 'group.dart';
+import 'metadata.dart';
+import 'suite_platform.dart';
+import 'test.dart';
+
+/// A test suite.
+///
+/// A test suite is a set of tests that are intended to be run together and that
+/// share default configuration.
+class Suite {
+ /// The platform on which the suite is running.
+ final SuitePlatform platform;
+
+ /// The path to the Dart test suite, or `null` if that path is unknown.
+ final String? path;
+
+ /// The metadata associated with this test suite.
+ ///
+ /// This is a shortcut for [group.metadata].
+ Metadata get metadata => group.metadata;
+
+ /// The top-level group for this test suite.
+ final Group group;
+
+ /// Whether or not to ignore test timeouts.
+ final bool ignoreTimeouts;
+
+ /// Creates a new suite containing [group].
+ ///
+ /// If [platform] and/or [os] are passed, [group] is filtered to match that
+ /// platform information.
+ ///
+ /// If [os] is passed without [platform], throws an [ArgumentError].
+ Suite(Group group, this.platform, {this.ignoreTimeouts = false, this.path})
+ : group = _filterGroup(group, platform);
+
+ /// Returns [entries] filtered according to [platform] and [os].
+ ///
+ /// Gracefully handles [platform] being null.
+ static Group _filterGroup(Group group, SuitePlatform platform) {
+ var filtered = group.forPlatform(platform);
+ if (filtered != null) return filtered;
+ return Group.root([], metadata: group.metadata);
+ }
+
+ /// Returns a new suite with all tests matching [test] removed.
+ ///
+ /// Unlike [GroupEntry.filter], this never returns `null`. If all entries are
+ /// filtered out, it returns an empty suite.
+ Suite filter(bool Function(Test) callback) {
+ var filtered = group.filter(callback);
+ filtered ??= Group.root([], metadata: metadata);
+ return Suite(filtered, platform,
+ ignoreTimeouts: ignoreTimeouts, path: path);
+ }
+
+ bool get isLoadSuite => false;
+}
diff --git a/pkgs/test_api/lib/src/backend/suite_channel_manager.dart b/pkgs/test_api/lib/src/backend/suite_channel_manager.dart
new file mode 100644
index 0000000..26a21c9
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/suite_channel_manager.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2018, 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 'package:stream_channel/stream_channel.dart';
+
+/// A class that connects incoming and outgoing channels with the same names.
+class SuiteChannelManager {
+ /// Connections from the test runner that have yet to connect to corresponding
+ /// calls to [connectOut].
+ final _incomingConnections = <String, StreamChannel<Object?>>{};
+
+ /// Connections from calls to [connectOut] that have yet to connect to
+ /// corresponding connections from the test runner.
+ final _outgoingConnections = <String, StreamChannelCompleter<Object?>>{};
+
+ /// The channel names that have already been used.
+ final _names = <String>{};
+
+ /// Creates a connection to the test runnner's channel with the given [name].
+ StreamChannel<Object?> connectOut(String name) {
+ if (_incomingConnections.containsKey(name)) {
+ return _incomingConnections[name]!;
+ } else if (_names.contains(name)) {
+ throw StateError('Duplicate suiteChannel() connection "$name".');
+ } else {
+ _names.add(name);
+ var completer = StreamChannelCompleter<Object?>();
+ _outgoingConnections[name] = completer;
+ return completer.channel;
+ }
+ }
+
+ /// Connects [channel] to this worker's channel with the given [name].
+ void connectIn(String name, StreamChannel<Object?> channel) {
+ if (_outgoingConnections.containsKey(name)) {
+ _outgoingConnections.remove(name)!.setChannel(channel);
+ } else if (_incomingConnections.containsKey(name)) {
+ throw StateError('Duplicate RunnerSuite.channel() connection "$name".');
+ } else {
+ _incomingConnections[name] = channel;
+ }
+ }
+}
diff --git a/pkgs/test_api/lib/src/backend/suite_platform.dart b/pkgs/test_api/lib/src/backend/suite_platform.dart
new file mode 100644
index 0000000..baab6ce
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/suite_platform.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2018, 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 'compiler.dart';
+import 'operating_system.dart';
+import 'runtime.dart';
+
+/// The platform on which a test suite is loaded.
+final class SuitePlatform {
+ /// The runtime that hosts the suite.
+ final Runtime runtime;
+
+ /// The operating system on which the suite is running.
+ ///
+ /// This will always be [OperatingSystem.none] if `runtime.isBrowser` is
+ /// true.
+ final OperatingSystem os;
+
+ /// Whether we're running on Google-internal infrastructure.
+ final bool inGoogle;
+
+ /// The compiler that should used for this platform.
+ final Compiler compiler;
+
+ /// Creates a new platform with the given [runtime] and [os], which defaults
+ /// to [OperatingSystem.none].
+ ///
+ /// Throws an [ArgumentError] if [runtime] is a browser and [os] is not
+ /// `null` or [OperatingSystem.none].
+ ///
+ /// If [compiler] is `null`, then the default compiler for [runtime] will be
+ /// used.
+ SuitePlatform(this.runtime,
+ {
+ // TODO(https://github.com/dart-lang/test/issues/1935): make required
+ Compiler? compiler,
+ this.os = OperatingSystem.none,
+ this.inGoogle = false})
+ : compiler = compiler ?? runtime.defaultCompiler {
+ if (runtime.isBrowser && os != OperatingSystem.none) {
+ throw ArgumentError('No OS should be passed for runtime "$runtime".');
+ }
+ if (!runtime.supportedCompilers.contains(this.compiler)) {
+ throw ArgumentError(
+ 'The platform $runtime does not support the compiler ${this.compiler}');
+ }
+ }
+
+ /// Converts a JSON-safe representation generated by [serialize] back into a
+ /// [SuitePlatform].
+ factory SuitePlatform.deserialize(Object serialized) {
+ var map = serialized as Map;
+ return SuitePlatform(Runtime.deserialize(map['runtime'] as Object),
+ compiler: map.containsKey('compiler')
+ ? Compiler.deserialize(map['compiler'] as Object)
+ : null,
+ os: OperatingSystem.find(map['os'] as String),
+ inGoogle: map['inGoogle'] as bool);
+ }
+
+ /// Converts [this] into a JSON-safe object that can be converted back to a
+ /// [SuitePlatform] using [SuitePlatform.deserialize].
+ Object serialize() => {
+ 'runtime': runtime.serialize(),
+ 'compiler': compiler.serialize(),
+ 'os': os.identifier,
+ 'inGoogle': inGoogle
+ };
+}
diff --git a/pkgs/test_api/lib/src/backend/test.dart b/pkgs/test_api/lib/src/backend/test.dart
new file mode 100644
index 0000000..5b5e73a
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+
+import 'group.dart';
+import 'group_entry.dart';
+import 'live_test.dart';
+import 'metadata.dart';
+import 'suite.dart';
+import 'suite_platform.dart';
+
+/// A single test.
+///
+/// A test is immutable and stateless, which means that it can't be run
+/// directly. To run one, load a live version using [Test.load] and run it using
+/// [LiveTest.run].
+abstract class Test implements GroupEntry {
+ @override
+ String get name;
+
+ @override
+ Metadata get metadata;
+
+ @override
+ Trace? get trace;
+
+ /// Loads a live version of this test, which can be used to run it a single
+ /// time.
+ ///
+ /// [suite] is the suite within which this test is being run. If [groups] is
+ /// passed, it's the list of groups containing this test; otherwise, it
+ /// defaults to just containing `suite.group`.
+ LiveTest load(Suite suite, {Iterable<Group>? groups});
+
+ @override
+ Test? forPlatform(SuitePlatform platform);
+
+ @override
+ Test? filter(bool Function(Test) callback) => callback(this) ? this : null;
+}
diff --git a/pkgs/test_api/lib/src/backend/test_failure.dart b/pkgs/test_api/lib/src/backend/test_failure.dart
new file mode 100644
index 0000000..4d9fc79
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/test_failure.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, 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.
+
+/// An exception thrown when a test assertion fails.
+class TestFailure implements Exception {
+ final String? message;
+
+ TestFailure(this.message);
+
+ @override
+ String toString() => message.toString();
+}
diff --git a/pkgs/test_api/lib/src/backend/util/identifier_regex.dart b/pkgs/test_api/lib/src/backend/util/identifier_regex.dart
new file mode 100644
index 0000000..6426641
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/util/identifier_regex.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2021, 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.
+
+/// A regular expression matching a full string as a hyphenated identifier.
+///
+/// This is like a standard Dart identifier, except that it can also contain
+/// hyphens.
+final anchoredHyphenatedIdentifier = RegExp(r'^[a-zA-Z_-][a-zA-Z0-9_-]*$');
diff --git a/pkgs/test_api/lib/src/backend/util/pretty_print.dart b/pkgs/test_api/lib/src/backend/util/pretty_print.dart
new file mode 100644
index 0000000..5c011c5
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/util/pretty_print.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2021, 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.
+
+/// Returns [name] if [number] is 1, or the plural of [name] otherwise.
+///
+/// By default, this just adds "s" to the end of [name] to get the plural. If
+/// [plural] is passed, that's used instead.
+String pluralize(String name, int number, {String? plural}) {
+ if (number == 1) return name;
+ if (plural != null) return plural;
+ return '${name}s';
+}
+
+/// Returns a sentence fragment listing the elements of [iter].
+///
+/// This converts each element of [iter] to a string and separates them with
+/// commas and/or [conjunction] where appropriate. The [conjunction] defaults to
+/// "and".
+String toSentence(Iterable iter, {String conjunction = 'and'}) {
+ if (iter.length == 1) return iter.first.toString();
+
+ var result = iter.take(iter.length - 1).join(', ');
+ if (iter.length > 2) result += ',';
+ return '$result $conjunction ${iter.last}';
+}
+
+/// Returns a human-friendly representation of [duration].
+String niceDuration(Duration duration) {
+ var minutes = duration.inMinutes;
+ var seconds = duration.inSeconds % 60;
+ var decaseconds = (duration.inMilliseconds % 1000) ~/ 100;
+
+ var buffer = StringBuffer();
+ if (minutes != 0) buffer.write('$minutes minutes');
+
+ if (minutes == 0 || seconds != 0) {
+ if (minutes != 0) buffer.write(', ');
+ buffer.write(seconds);
+ if (decaseconds != 0) buffer.write('.$decaseconds');
+ buffer.write(' seconds');
+ }
+
+ return buffer.toString();
+}
diff --git a/pkgs/test_api/lib/src/frontend/fake.dart b/pkgs/test_api/lib/src/frontend/fake.dart
new file mode 100644
index 0000000..e3e0ea6
--- /dev/null
+++ b/pkgs/test_api/lib/src/frontend/fake.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2020, 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.
+
+/// A stand-in for another object which cannot be used except for specifically
+/// overridden methods.
+///
+/// A fake has a default behavior for every field and method of throwing
+/// [UnimplementedError]. Fields and methods that are exercised by the code
+/// under test should be manually overridden in the implementing class.
+///
+/// A fake does not have any support for verification or defining behavior from
+/// the test, it cannot be used as a mock.
+///
+/// In most cases a shared full fake implementation without a `noSuchMethod` is
+/// preferable to `extends Fake`, however `extends Fake` is preferred against
+/// `extends Mock` mixed with manual `@override` implementations.
+///
+/// __Example use__:
+///
+/// // Real class.
+/// class Cat {
+/// String meow(String suffix) => 'Meow$suffix';
+/// String hiss(String suffix) => 'Hiss$suffix';
+/// }
+///
+/// // Fake class.
+/// class FakeCat extends Fake implements Cat {
+/// @override
+/// String meow(String suffix) => 'FakeMeow$suffix';
+/// }
+///
+/// void main() {
+/// // Create a new fake Cat at runtime.
+/// var cat = new FakeCat();
+///
+/// // Try making a Cat sound...
+/// print(cat.meow('foo')); // Prints 'FakeMeowfoo'
+/// print(cat.hiss('foo')); // Throws
+/// }
+///
+/// **WARNING**: [Fake] uses [noSuchMethod](goo.gl/r3IQUH), which is a _form_ of
+/// runtime reflection, and causes sub-standard code to be generated. As such,
+/// [Fake] should strictly _not_ be used in any production code, especially if
+/// used within the context of Dart for Web (dart2js, DDC) and Dart for Mobile
+/// (Flutter).
+abstract mixin class Fake {
+ @override
+ dynamic noSuchMethod(Invocation invocation) {
+ throw UnimplementedError(invocation.memberName.toString().split('"')[1]);
+ }
+}
diff --git a/pkgs/test_api/lib/src/remote_listener.dart b/pkgs/test_api/lib/src/remote_listener.dart
new file mode 100644
index 0000000..f2df134
--- /dev/null
+++ b/pkgs/test_api/lib/src/remote_listener.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2021, 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.
+
+export 'backend/remote_listener.dart' show RemoteListener;
diff --git a/pkgs/test_api/lib/src/scaffolding/spawn_hybrid.dart b/pkgs/test_api/lib/src/scaffolding/spawn_hybrid.dart
new file mode 100644
index 0000000..7222d52
--- /dev/null
+++ b/pkgs/test_api/lib/src/scaffolding/spawn_hybrid.dart
@@ -0,0 +1,179 @@
+// Copyright (c) 2016, 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 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+import '../backend/remote_exception.dart';
+import '../utils.dart';
+import 'test_structure.dart' show addTearDown;
+
+/// A transformer that handles messages from the spawned isolate and ensures
+/// that messages sent to it are JSON-encodable.
+///
+/// The spawned isolate sends three kinds of messages. Data messages are emitted
+/// as data events, error messages are emitted as error events, and print
+/// messages are printed using `print()`.
+// package:test will only send a `Map` across this channel, but users of
+// `hybridMain` can send any json encodeable type.
+final _transformer = StreamChannelTransformer<dynamic, dynamic>(
+ StreamTransformer.fromHandlers(handleData: (message, sink) {
+ switch (message['type'] as String) {
+ case 'data':
+ sink.add(message['data']);
+ break;
+
+ case 'print':
+ print(message['line']);
+ break;
+
+ case 'error':
+ var error = RemoteException.deserialize(message['error'] as Map);
+ sink.addError(error.error, error.stackTrace);
+ break;
+ }
+}), StreamSinkTransformer.fromHandlers(handleData: (message, sink) {
+ // This is called synchronously from the user's `Sink.add()` call, so if
+ // [ensureJsonEncodable] throws here they'll get a helpful stack trace.
+ ensureJsonEncodable(message);
+ sink.add(message);
+}));
+
+/// Spawns a VM isolate for the given [uri], which may be a [Uri] or a [String].
+///
+/// This allows browser tests to spawn servers with which they can communicate
+/// to test client/server interactions. It can also be used by VM tests to
+/// easily spawn an isolate.
+///
+/// The Dart file at [uri] must define a top-level `hybridMain()` function that
+/// takes a `StreamChannel` argument and, optionally, an `Object` argument to
+/// which [message] will be passed. Note that [message] must be JSON-encodable.
+/// For example:
+///
+/// ```dart
+/// import "package:stream_channel/stream_channel.dart";
+///
+/// hybridMain(StreamChannel channel, Object message) {
+/// // ...
+/// }
+/// ```
+///
+/// If [uri] is relative, it will be interpreted relative to the `file:` URL for
+/// the test suite being executed. If it's root-relative (that is, if it begins
+/// with `/`) it will be interpreted relative to the root of the package (the
+/// directory that contains `pubspec.yaml`, *not* the `test/` directory). If
+/// it's a `package:` URL, it will be resolved using the current package's
+/// dependency constellation.
+///
+/// Returns a [StreamChannel] that's connected to the channel passed to
+/// `hybridMain()`. Only JSON-encodable objects may be sent through this
+/// channel. If the channel is closed, the hybrid isolate is killed. If the
+/// isolate is killed, the channel's stream will emit a "done" event.
+///
+/// Any unhandled errors loading or running the hybrid isolate will be emitted
+/// as errors over the channel's stream. Any calls to `print()` in the hybrid
+/// isolate will be printed as though they came from the test that created the
+/// isolate.
+///
+/// Code in the hybrid isolate is not considered to be running in a test
+/// context, so it can't access test functions like `expect()` and
+/// `expectAsync()`.
+///
+/// By default, the hybrid isolate is automatically killed when the test
+/// finishes running. If [stayAlive] is `true`, it won't be killed until the
+/// entire test suite finishes running.
+///
+/// **Note**: If you use this API, be sure to add a dependency on the
+/// **`stream_channel` package, since you're using its API as well!
+StreamChannel spawnHybridUri(
+ Object uri, {
+ Object? message,
+ bool stayAlive = false,
+}) {
+ if (uri is String) {
+ // Ensure that it can be parsed as a uri.
+ Uri.parse(uri);
+ } else if (uri is! Uri) {
+ throw ArgumentError.value(uri, 'uri', 'must be a Uri or a String.');
+ }
+ return _spawn(uri.toString(), message, stayAlive: stayAlive);
+}
+
+/// Spawns a VM isolate that runs the given [dartCode], which is loaded as the
+/// contents of a Dart library.
+///
+/// This allows browser tests to spawn servers with which they can communicate
+/// to test client/server interactions. It can also be used by VM tests to
+/// easily spawn an isolate.
+///
+/// The [dartCode] must define a top-level `hybridMain()` function that takes a
+/// `StreamChannel` argument and, optionally, an `Object` argument to which
+/// [message] will be passed. Note that [message] must be JSON-encodable. For
+/// example:
+///
+/// ```dart
+/// import "package:stream_channel/stream_channel.dart";
+///
+/// hybridMain(StreamChannel channel, Object message) {
+/// // ...
+/// }
+/// ```
+///
+/// Returns a [StreamChannel] that's connected to the channel passed to
+/// `hybridMain()`. Only JSON-encodable objects may be sent through this
+/// channel. If the channel is closed, the hybrid isolate is killed. If the
+/// isolate is killed, the channel's stream will emit a "done" event.
+///
+/// Any unhandled errors loading or running the hybrid isolate will be emitted
+/// as errors over the channel's stream. Any calls to `print()` in the hybrid
+/// isolate will be printed as though they came from the test that created the
+/// isolate.
+///
+/// Code in the hybrid isolate is not considered to be running in a test
+/// context, so it can't access test functions like `expect()` and
+/// `expectAsync()`.
+///
+/// By default, the hybrid isolate is automatically killed when the test
+/// finishes running. If [stayAlive] is `true`, it won't be killed until the
+/// entire test suite finishes running.
+///
+/// **Note**: If you use this API, be sure to add a dependency on the
+/// **`stream_channel` package, since you're using its API as well!
+StreamChannel spawnHybridCode(String dartCode,
+ {Object? message, bool stayAlive = false}) {
+ var uri = Uri.dataFromString(dartCode,
+ encoding: utf8, mimeType: 'application/dart');
+ return _spawn(uri.toString(), message, stayAlive: stayAlive);
+}
+
+/// Like [spawnHybridUri], but doesn't take [Uri] objects.
+StreamChannel _spawn(String uri, Object? message, {bool stayAlive = false}) {
+ var channel = Zone.current[#test.runner.test_channel] as MultiChannel?;
+ if (channel == null) {
+ throw UnsupportedError("Can't connect to the test runner.\n"
+ 'spawnHybridUri() is currently only supported within "dart test".');
+ }
+
+ ensureJsonEncodable(message);
+
+ var virtualChannel = channel.virtualChannel();
+ StreamChannel isolateChannel = virtualChannel;
+ channel.sink.add({
+ 'type': 'spawn-hybrid-uri',
+ 'url': uri,
+ 'message': message,
+ 'channel': virtualChannel.id
+ });
+
+ if (!stayAlive) {
+ var disconnector = Disconnector<void>();
+ addTearDown(() => disconnector.disconnect());
+ isolateChannel = isolateChannel.transform(disconnector);
+ }
+
+ return isolateChannel.transform(_transformer);
+}
diff --git a/pkgs/test_api/lib/src/scaffolding/test_structure.dart b/pkgs/test_api/lib/src/scaffolding/test_structure.dart
new file mode 100644
index 0000000..642e013
--- /dev/null
+++ b/pkgs/test_api/lib/src/scaffolding/test_structure.dart
@@ -0,0 +1,251 @@
+// Copyright (c) 2021, 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 'package:meta/meta.dart';
+
+import '../backend/configuration/timeout.dart';
+import '../backend/declarer.dart';
+import '../backend/invoker.dart';
+
+// test_core does not support running tests directly, so the Declarer should
+// always be on the Zone.
+Declarer get _declarer => Zone.current[#test.declarer] as Declarer;
+
+// TODO(nweiz): This and other top-level functions should throw exceptions if
+// they're called after the declarer has finished declaring.
+/// Creates a new test case with the given description (converted to a string)
+/// and body.
+///
+/// The description will be added to the descriptions of any surrounding
+/// [group]s. If [testOn] is passed, it's parsed as a [platform selector][]; the
+/// test will only be run on matching platforms.
+///
+/// [platform selector]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-selectors
+///
+/// If [timeout] is passed, it's used to modify or replace the default timeout
+/// of 30 seconds. Timeout modifications take precedence in suite-group-test
+/// order, so [timeout] will also modify any timeouts set on the group or suite.
+///
+/// If [skip] is a String or `true`, the test is skipped. If it's a String, it
+/// should explain why the test is skipped; this reason will be printed instead
+/// of running the test.
+///
+/// If [tags] is passed, it declares user-defined tags that are applied to the
+/// test. These tags can be used to select or skip the test on the command line,
+/// or to do bulk test configuration. All tags should be declared in the
+/// [package configuration file][configuring tags]. The parameter can be an
+/// [Iterable] of tag names, or a [String] representing a single tag.
+///
+/// If [retry] is passed, the test will be retried the provided number of times
+/// before being marked as a failure.
+///
+/// [configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags
+///
+/// [onPlatform] allows tests to be configured on a platform-by-platform
+/// basis. It's a map from strings that are parsed as [PlatformSelector]s to
+/// annotation classes: [Timeout], [Skip], or lists of those. These
+/// annotations apply only on the given platforms. For example:
+///
+/// test('potentially slow test', () {
+/// // ...
+/// }, onPlatform: {
+/// // This test is especially slow on Windows.
+/// 'windows': Timeout.factor(2),
+/// 'browser': [
+/// Skip('TODO: add browser support'),
+/// // This will be slow on browsers once it works on them.
+/// Timeout.factor(2)
+/// ]
+/// });
+///
+/// If multiple platforms match, the annotations apply in order as through
+/// they were in nested groups.
+///
+/// If the `solo` flag is `true`, only tests and groups marked as
+/// "solo" will be be run. This only restricts tests *within this test
+/// suite*—tests in other suites will run as normal. We recommend that users
+/// avoid this flag if possible and instead use the test runner flag `-n` to
+/// filter tests by name.
+@isTest
+void test(Object? description, dynamic Function() body,
+ {String? testOn,
+ Timeout? timeout,
+ Object? skip,
+ Object? tags,
+ Map<String, dynamic>? onPlatform,
+ int? retry,
+ // TODO(https://github.com/dart-lang/test/issues/2205): Remove deprecated.
+ @Deprecated('Debug only') @doNotSubmit bool solo = false}) {
+ _declarer.test(description.toString(), body,
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ onPlatform: onPlatform,
+ tags: tags,
+ retry: retry,
+ solo: solo);
+
+ // Force dart2js not to inline this function. We need it to be separate from
+ // `main()` in JS stack traces in order to properly determine the line and
+ // column where the test was defined. See sdk#26705.
+ return;
+ return; // ignore: dead_code
+}
+
+/// Creates a group of tests.
+///
+/// A group's description (converted to a string) is included in the descriptions
+/// of any tests or sub-groups it contains. [setUp] and [tearDown] are also scoped
+/// to the containing group.
+///
+/// If [testOn] is passed, it's parsed as a [platform selector][]; the test will
+/// only be run on matching platforms.
+///
+/// [platform selector]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-selectors
+///
+/// If [timeout] is passed, it's used to modify or replace the default timeout
+/// of 30 seconds. Timeout modifications take precedence in suite-group-test
+/// order, so [timeout] will also modify any timeouts set on the suite, and will
+/// be modified by any timeouts set on individual tests.
+///
+/// If [skip] is a String or `true`, the group is skipped. If it's a String, it
+/// should explain why the group is skipped; this reason will be printed instead
+/// of running the group's tests.
+///
+/// If [tags] is passed, it declares user-defined tags that are applied to the
+/// test. These tags can be used to select or skip the test on the command line,
+/// or to do bulk test configuration. All tags should be declared in the
+/// [package configuration file][configuring tags]. The parameter can be an
+/// [Iterable] of tag names, or a [String] representing a single tag.
+///
+/// [configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags
+///
+/// [onPlatform] allows groups to be configured on a platform-by-platform
+/// basis. It's a map from strings that are parsed as [PlatformSelector]s to
+/// annotation classes: [Timeout], [Skip], or lists of those. These
+/// annotations apply only on the given platforms. For example:
+///
+/// group('potentially slow tests', () {
+/// // ...
+/// }, onPlatform: {
+/// // These tests are especially slow on Windows.
+/// 'windows': Timeout.factor(2),
+/// 'browser': [
+/// Skip('TODO: add browser support'),
+/// // They'll be slow on browsers once it works on them.
+/// Timeout.factor(2)
+/// ]
+/// });
+///
+/// If multiple platforms match, the annotations apply in order as through
+/// they were in nested groups.
+///
+/// If the `solo` flag is `true`, only tests and groups marked as
+/// "solo" will be be run. This only restricts tests *within this test
+/// suite*—tests in other suites will run as normal. We recommend that users
+/// avoid this flag if possible, and instead use the test runner flag `-n` to
+/// filter tests by name.
+@isTestGroup
+void group(Object? description, dynamic Function() body,
+ {String? testOn,
+ Timeout? timeout,
+ Object? skip,
+ Object? tags,
+ Map<String, dynamic>? onPlatform,
+ int? retry,
+ // TODO(https://github.com/dart-lang/test/issues/2205): Remove deprecated.
+ @Deprecated('Debug only') @doNotSubmit bool solo = false}) {
+ _declarer.group(description.toString(), body,
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ tags: tags,
+ onPlatform: onPlatform,
+ retry: retry,
+ solo: solo);
+
+ // Force dart2js not to inline this function. We need it to be separate from
+ // `main()` in JS stack traces in order to properly determine the line and
+ // column where the test was defined. See sdk#26705.
+ return;
+ return; // ignore: dead_code
+}
+
+/// Registers a function to be run before tests.
+///
+/// This function will be called before each test is run. [callback] may be
+/// asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, it applies only to tests in that
+/// group. [callback] will be run after any set-up callbacks in parent groups or
+/// at the top level.
+///
+/// Each callback at the top level or in a given group will be run in the order
+/// they were declared.
+void setUp(dynamic Function() callback) => _declarer.setUp(callback);
+
+/// Registers a function to be run after tests.
+///
+/// This function will be called after each test is run. [callback] may be
+/// asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, it applies only to tests in that
+/// group. [callback] will be run before any tear-down callbacks in parent
+/// groups or at the top level.
+///
+/// Each callback at the top level or in a given group will be run in the
+/// reverse of the order they were declared.
+///
+/// See also [addTearDown], which adds tear-downs to a running test.
+void tearDown(dynamic Function() callback) => _declarer.tearDown(callback);
+
+/// Registers a function to be run after the current test.
+///
+/// This is called within a running test, and adds a tear-down only for the
+/// current test. It allows testing libraries to add cleanup logic as soon as
+/// there's something to clean up.
+///
+/// The [callback] is run before any callbacks registered with [tearDown]. Like
+/// [tearDown], the most recently registered callback is run first.
+///
+/// If this is called from within a [setUpAll] or [tearDownAll] callback, it
+/// instead runs the function after *all* tests in the current test suite.
+void addTearDown(dynamic Function() callback) {
+ if (Invoker.current == null) {
+ throw StateError('addTearDown() may only be called within a test.');
+ }
+
+ Invoker.current!.addTearDown(callback);
+}
+
+/// Registers a function to be run once before all tests.
+///
+/// [callback] may be asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, [callback] will run before all tests
+/// in that group. It will be run after any [setUpAll] callbacks in parent
+/// groups or at the top level. It won't be run if none of the tests in the
+/// group are run.
+///
+/// **Note**: This function makes it very easy to accidentally introduce hidden
+/// dependencies between tests that should be isolated. In general, you should
+/// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
+/// slow.
+void setUpAll(dynamic Function() callback) => _declarer.setUpAll(callback);
+
+/// Registers a function to be run once after all tests.
+///
+/// If this is called within a test group, [callback] will run after all tests
+/// in that group. It will be run before any [tearDownAll] callbacks in parent
+/// groups or at the top level. It won't be run if none of the tests in the
+/// group are run.
+///
+/// **Note**: This function makes it very easy to accidentally introduce hidden
+/// dependencies between tests that should be isolated. In general, you should
+/// prefer [tearDown], and only use [tearDownAll] if the callback is
+/// prohibitively slow.
+void tearDownAll(dynamic Function() callback) =>
+ _declarer.tearDownAll(callback);
diff --git a/pkgs/test_api/lib/src/scaffolding/utils.dart b/pkgs/test_api/lib/src/scaffolding/utils.dart
new file mode 100644
index 0000000..134dc49
--- /dev/null
+++ b/pkgs/test_api/lib/src/scaffolding/utils.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2021, 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 '../backend/invoker.dart';
+
+/// Returns a [Future] that completes after the [event loop][] has run the given
+/// number of [times] (20 by default).
+///
+/// [event loop]: https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a
+///
+/// Awaiting this approximates waiting until all asynchronous work (other than
+/// work that's waiting for external resources) completes.
+Future pumpEventQueue({int times = 20}) {
+ if (times == 0) return Future.value();
+ // Use the event loop to allow the microtask queue to finish.
+ return Future(() => pumpEventQueue(times: times - 1));
+}
+
+/// Registers an exception that was caught for the current test.
+void registerException(Object error,
+ [StackTrace stackTrace = StackTrace.empty]) {
+ // This will usually forward directly to [Invoker.current.handleError], but
+ // going through the zone API allows other zones to consistently see errors.
+ Zone.current.handleUncaughtError(error, stackTrace);
+}
+
+/// Prints [message] if and when the current test fails.
+///
+/// This is intended for test infrastructure to provide debugging information
+/// without cluttering the output for successful tests. Note that unlike
+/// [print], each individual message passed to [printOnFailure] will be
+/// separated by a blank line.
+void printOnFailure(String message) {
+ _currentInvoker.printOnFailure(message);
+}
+
+/// Marks the current test as skipped.
+///
+/// A skipped test may still fail if any exception is thrown, including uncaught
+/// asynchronous errors. If the entire test should be skipped `return` from the
+/// test body after marking it as skipped.
+void markTestSkipped(String message) => _currentInvoker..skip(message);
+
+Invoker get _currentInvoker =>
+ Invoker.current ??
+ (throw StateError(
+ 'There is no current invoker. Please make sure that you are making the '
+ 'call inside a test zone.'));
diff --git a/pkgs/test_api/lib/src/utils.dart b/pkgs/test_api/lib/src/utils.dart
new file mode 100644
index 0000000..3bf7518
--- /dev/null
+++ b/pkgs/test_api/lib/src/utils.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2013, 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.
+
+/// Throws an [ArgumentError] if [message] isn't recursively JSON-safe.
+void ensureJsonEncodable(Object? message) {
+ if (message == null ||
+ message is String ||
+ message is num ||
+ message is bool) {
+ // JSON-encodable, hooray!
+ } else if (message is List) {
+ for (var element in message) {
+ ensureJsonEncodable(element);
+ }
+ } else if (message is Map) {
+ message.forEach((key, value) {
+ if (key is! String) {
+ throw ArgumentError("$message can't be JSON-encoded.");
+ }
+
+ ensureJsonEncodable(value);
+ });
+ } else {
+ throw ArgumentError.value("$message can't be JSON-encoded.");
+ }
+}
diff --git a/pkgs/test_api/lib/test_api.dart b/pkgs/test_api/lib/test_api.dart
new file mode 100644
index 0000000..75c1540
--- /dev/null
+++ b/pkgs/test_api/lib/test_api.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2013, 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.
+
+@Deprecated('package:test_api is not intended for general use. '
+ 'Please use package:test.')
+library;
+
+export 'hooks.dart' show TestFailure;
+export 'scaffolding.dart';
diff --git a/pkgs/test_api/mono_pkg.yaml b/pkgs/test_api/mono_pkg.yaml
new file mode 100644
index 0000000..534d9c3
--- /dev/null
+++ b/pkgs/test_api/mono_pkg.yaml
@@ -0,0 +1,19 @@
+# See https://pub.dev/packages/mono_repo
+
+sdk:
+- dev
+- pubspec
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+ sdk:
+ - dev
+- unit_test:
+ - group:
+ - command: dart test --preset travis -x browser
+ os:
+ - linux
+ - windows
diff --git a/pkgs/test_api/pubspec.yaml b/pkgs/test_api/pubspec.yaml
new file mode 100644
index 0000000..af5cb93
--- /dev/null
+++ b/pkgs/test_api/pubspec.yaml
@@ -0,0 +1,29 @@
+name: test_api
+version: 0.7.4
+description: >-
+ The user facing API for structuring Dart tests and checking expectations.
+repository: https://github.com/dart-lang/test/tree/master/pkgs/test_api
+resolution: workspace
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ async: ^2.5.0
+ boolean_selector: ^2.1.0
+ collection: ^1.15.0
+ meta: ^1.14.0
+ source_span: ^1.8.0
+ stack_trace: ^1.10.0
+ stream_channel: ^2.1.0
+ string_scanner: ^1.1.0
+ term_glyph: ^1.2.0
+
+dev_dependencies:
+ analyzer: '>=6.0.0 <8.0.0'
+ fake_async: ^1.2.0
+ glob: ^2.0.0
+ graphs: ^2.0.0
+ path: ^1.8.0
+ test: any
+ test_core: any
diff --git a/pkgs/test_api/test/backend/declarer_test.dart b/pkgs/test_api/test/backend/declarer_test.dart
new file mode 100644
index 0000000..f095ad0
--- /dev/null
+++ b/pkgs/test_api/test/backend/declarer_test.dart
@@ -0,0 +1,703 @@
+// Copyright (c) 2015, 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 'package:test/test.dart';
+import 'package:test_api/src/backend/declarer.dart';
+import 'package:test_api/src/backend/group.dart';
+import 'package:test_api/src/backend/invoker.dart';
+import 'package:test_api/src/backend/suite.dart';
+import 'package:test_api/src/backend/test.dart';
+
+import '../utils.dart';
+
+late Suite _suite;
+
+void main() {
+ setUp(() {
+ _suite = Suite(Group.root([]), suitePlatform, ignoreTimeouts: false);
+ });
+
+ group('.test()', () {
+ test('declares a test with a description and body', () async {
+ var bodyRun = false;
+ var tests = declare(() {
+ test('description', () {
+ bodyRun = true;
+ });
+ });
+
+ expect(tests, hasLength(1));
+ expect(tests.single.name, equals('description'));
+
+ await _runTest(tests[0] as Test);
+ expect(bodyRun, isTrue);
+ });
+
+ test('declares a test with an object as the description', () async {
+ var tests = declare(() {
+ test(Object, () {});
+ });
+
+ expect(tests.single.name, equals('Object'));
+ });
+
+ test('declares multiple tests', () {
+ var tests = declare(() {
+ test('description 1', () {});
+ test('description 2', () {});
+ test('description 3', () {});
+ });
+
+ expect(tests, hasLength(3));
+ expect(tests[0].name, equals('description 1'));
+ expect(tests[1].name, equals('description 2'));
+ expect(tests[2].name, equals('description 3'));
+ });
+ });
+
+ group('.setUp()', () {
+ test('is run before all tests', () async {
+ var setUpRun = false;
+ var tests = declare(() {
+ setUp(() => setUpRun = true);
+
+ test(
+ 'description 1',
+ expectAsync0(() {
+ expect(setUpRun, isTrue);
+ setUpRun = false;
+ }, max: 1));
+
+ test(
+ 'description 2',
+ expectAsync0(() {
+ expect(setUpRun, isTrue);
+ setUpRun = false;
+ }, max: 1));
+ });
+
+ await _runTest(tests[0] as Test);
+ await _runTest(tests[1] as Test);
+ });
+
+ test('can return a Future', () {
+ var setUpRun = false;
+ var tests = declare(() {
+ setUp(() {
+ return Future(() => setUpRun = true);
+ });
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(setUpRun, isTrue);
+ }, max: 1));
+ });
+
+ return _runTest(tests.single as Test);
+ });
+
+ test('runs in call order within a group', () async {
+ var firstSetUpRun = false;
+ var secondSetUpRun = false;
+ var thirdSetUpRun = false;
+ var tests = declare(() {
+ setUp(expectAsync0(() async {
+ expect(secondSetUpRun, isFalse);
+ expect(thirdSetUpRun, isFalse);
+ firstSetUpRun = true;
+ }));
+
+ setUp(expectAsync0(() async {
+ expect(firstSetUpRun, isTrue);
+ expect(thirdSetUpRun, isFalse);
+ secondSetUpRun = true;
+ }));
+
+ setUp(expectAsync0(() async {
+ expect(firstSetUpRun, isTrue);
+ expect(secondSetUpRun, isTrue);
+ thirdSetUpRun = true;
+ }));
+
+ test('description', expectAsync0(() {
+ expect(firstSetUpRun, isTrue);
+ expect(secondSetUpRun, isTrue);
+ expect(thirdSetUpRun, isTrue);
+ }));
+ });
+
+ await _runTest(tests.single as Test);
+ });
+ });
+
+ group('.tearDown()', () {
+ test('is run after all tests', () async {
+ late bool tearDownRun;
+ var tests = declare(() {
+ setUp(() => tearDownRun = false);
+ tearDown(() => tearDownRun = true);
+
+ test(
+ 'description 1',
+ expectAsync0(() {
+ expect(tearDownRun, isFalse);
+ }, max: 1));
+
+ test(
+ 'description 2',
+ expectAsync0(() {
+ expect(tearDownRun, isFalse);
+ }, max: 1));
+ });
+
+ await _runTest(tests[0] as Test);
+ expect(tearDownRun, isTrue);
+ await _runTest(tests[1] as Test);
+ expect(tearDownRun, isTrue);
+ });
+
+ test('is run after an out-of-band failure', () async {
+ late bool tearDownRun;
+ var tests = declare(() {
+ setUp(() => tearDownRun = false);
+ tearDown(() => tearDownRun = true);
+
+ test(
+ 'description 1',
+ expectAsync0(() {
+ Invoker.current!.addOutstandingCallback();
+ Future(() => throw TestFailure('oh no'))
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ }, max: 1));
+ });
+
+ await _runTest(tests.single as Test, shouldFail: true);
+ expect(tearDownRun, isTrue);
+ });
+
+ test('can return a Future', () async {
+ var tearDownRun = false;
+ var tests = declare(() {
+ tearDown(() {
+ return Future(() => tearDownRun = true);
+ });
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(tearDownRun, isFalse);
+ }, max: 1));
+ });
+
+ await _runTest(tests.single as Test);
+ expect(tearDownRun, isTrue);
+ });
+
+ test("isn't run until there are no outstanding callbacks", () async {
+ var outstandingCallbackRemoved = false;
+ var outstandingCallbackRemovedBeforeTeardown = false;
+ var tests = declare(() {
+ tearDown(() {
+ outstandingCallbackRemovedBeforeTeardown = outstandingCallbackRemoved;
+ });
+
+ test('description', () {
+ Invoker.current!.addOutstandingCallback();
+ pumpEventQueue().then((_) {
+ outstandingCallbackRemoved = true;
+ Invoker.current!.removeOutstandingCallback();
+ });
+ });
+ });
+
+ await _runTest(tests.single as Test);
+ expect(outstandingCallbackRemovedBeforeTeardown, isTrue);
+ });
+
+ test("isn't run until test body completes after out-of-band error",
+ () async {
+ var hasTestFinished = false;
+ var hasTestFinishedBeforeTeardown = false;
+ var tests = declare(() {
+ tearDown(() {
+ hasTestFinishedBeforeTeardown = hasTestFinished;
+ });
+
+ test('description', () {
+ Future<Never>.error('oh no');
+ return pumpEventQueue().then((_) {
+ hasTestFinished = true;
+ });
+ });
+ });
+
+ await _runTest(tests.single as Test, shouldFail: true);
+ expect(hasTestFinishedBeforeTeardown, isTrue);
+ });
+
+ test("doesn't complete until there are no outstanding callbacks", () async {
+ var outstandingCallbackRemoved = false;
+ var tests = declare(() {
+ tearDown(() {
+ Invoker.current!.addOutstandingCallback();
+ pumpEventQueue().then((_) {
+ outstandingCallbackRemoved = true;
+ Invoker.current!.removeOutstandingCallback();
+ });
+ });
+
+ test('description', () {});
+ });
+
+ await _runTest(tests.single as Test);
+ expect(outstandingCallbackRemoved, isTrue);
+ });
+
+ test('runs in reverse call order within a group', () async {
+ var firstTearDownRun = false;
+ var secondTearDownRun = false;
+ var thirdTearDownRun = false;
+ var tests = declare(() {
+ tearDown(expectAsync0(() async {
+ expect(secondTearDownRun, isTrue);
+ expect(thirdTearDownRun, isTrue);
+ firstTearDownRun = true;
+ }));
+
+ tearDown(expectAsync0(() async {
+ expect(firstTearDownRun, isFalse);
+ expect(thirdTearDownRun, isTrue);
+ secondTearDownRun = true;
+ }));
+
+ tearDown(expectAsync0(() async {
+ expect(firstTearDownRun, isFalse);
+ expect(secondTearDownRun, isFalse);
+ thirdTearDownRun = true;
+ }));
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(firstTearDownRun, isFalse);
+ expect(secondTearDownRun, isFalse);
+ expect(thirdTearDownRun, isFalse);
+ }, max: 1));
+ });
+
+ await _runTest(tests.single as Test);
+ });
+
+ test('runs further tearDowns in a group even if one fails', () async {
+ var tests = declare(() {
+ tearDown(expectAsync0(() {}));
+
+ tearDown(() async {
+ throw 'error';
+ });
+
+ test('description', expectAsync0(() {}));
+ });
+
+ await _runTest(tests.single as Test, shouldFail: true);
+ });
+
+ test('runs in the same error zone as the test', () {
+ return expectTestsPass(() {
+ late Zone testBodyZone;
+
+ tearDown(() {
+ final tearDownZone = Zone.current;
+ expect(tearDownZone.inSameErrorZone(testBodyZone), isTrue,
+ reason: 'The tear down callback is in a different error zone '
+ 'than the test body.');
+ });
+
+ test('test', () {
+ testBodyZone = Zone.current;
+ });
+ });
+ });
+ });
+
+ group('in a group,', () {
+ test("tests inherit the group's description", () {
+ var entries = declare(() {
+ group('group', () {
+ test('description', () {});
+ });
+ });
+
+ expect(entries, hasLength(1));
+ var testGroup = entries.single as Group;
+ expect(testGroup.name, equals('group'));
+ expect(testGroup.entries, hasLength(1));
+ expect(testGroup.entries.single, const TypeMatcher<Test>());
+ expect(testGroup.entries.single.name, 'group description');
+ });
+
+ test("tests inherit the group's description when it's not a string", () {
+ var entries = declare(() {
+ group(Object, () {
+ test('description', () {});
+ });
+ });
+
+ expect(entries, hasLength(1));
+ var testGroup = entries.single as Group;
+ expect(testGroup.name, equals('Object'));
+ expect(testGroup.entries, hasLength(1));
+ expect(testGroup.entries.single, const TypeMatcher<Test>());
+ expect(testGroup.entries.single.name, 'Object description');
+ });
+
+ test("a test's timeout factor is applied to the group's", () {
+ var entries = declare(() {
+ group('group', () {
+ test('test', () {}, timeout: const Timeout.factor(3));
+ }, timeout: const Timeout.factor(2));
+ });
+
+ expect(entries, hasLength(1));
+ var testGroup = entries.single as Group;
+ expect(testGroup.metadata.timeout.scaleFactor, equals(2));
+ expect(testGroup.entries, hasLength(1));
+ expect(testGroup.entries.single, const TypeMatcher<Test>());
+ expect(testGroup.entries.single.metadata.timeout.scaleFactor, equals(6));
+ });
+
+ test("a test's timeout factor is applied to the group's duration", () {
+ var entries = declare(() {
+ group('group', () {
+ test('test', () {}, timeout: const Timeout.factor(2));
+ }, timeout: const Timeout(Duration(seconds: 10)));
+ });
+
+ expect(entries, hasLength(1));
+ var testGroup = entries.single as Group;
+ expect(testGroup.metadata.timeout.duration,
+ equals(const Duration(seconds: 10)));
+ expect(testGroup.entries, hasLength(1));
+ expect(testGroup.entries.single, const TypeMatcher<Test>());
+ expect(testGroup.entries.single.metadata.timeout.duration,
+ equals(const Duration(seconds: 20)));
+ });
+
+ test("a test's timeout duration is applied over the group's", () {
+ var entries = declare(() {
+ group('group', () {
+ test('test', () {}, timeout: const Timeout(Duration(seconds: 15)));
+ }, timeout: const Timeout(Duration(seconds: 10)));
+ });
+
+ expect(entries, hasLength(1));
+ var testGroup = entries.single as Group;
+ expect(testGroup.metadata.timeout.duration,
+ equals(const Duration(seconds: 10)));
+ expect(testGroup.entries, hasLength(1));
+ expect(testGroup.entries.single, const TypeMatcher<Test>());
+ expect(testGroup.entries.single.metadata.timeout.duration,
+ equals(const Duration(seconds: 15)));
+ });
+
+ test('disallows asynchronous groups', () async {
+ declare(() {
+ expect(() => group('group', () async {}), throwsArgumentError);
+ });
+ });
+
+ group('.setUp()', () {
+ test('is scoped to the group', () async {
+ var setUpRun = false;
+ var entries = declare(() {
+ group('group', () {
+ setUp(() => setUpRun = true);
+
+ test(
+ 'description 1',
+ expectAsync0(() {
+ expect(setUpRun, isTrue);
+ setUpRun = false;
+ }, max: 1));
+ });
+
+ test(
+ 'description 2',
+ expectAsync0(() {
+ expect(setUpRun, isFalse);
+ setUpRun = false;
+ }, max: 1));
+ });
+
+ await _runTest((entries[0] as Group).entries.single as Test);
+ await _runTest(entries[1] as Test);
+ });
+
+ test('runs from the outside in', () {
+ var outerSetUpRun = false;
+ var middleSetUpRun = false;
+ var innerSetUpRun = false;
+ var entries = declare(() {
+ setUp(expectAsync0(() {
+ expect(middleSetUpRun, isFalse);
+ expect(innerSetUpRun, isFalse);
+ outerSetUpRun = true;
+ }, max: 1));
+
+ group('middle', () {
+ setUp(expectAsync0(() {
+ expect(outerSetUpRun, isTrue);
+ expect(innerSetUpRun, isFalse);
+ middleSetUpRun = true;
+ }, max: 1));
+
+ group('inner', () {
+ setUp(expectAsync0(() {
+ expect(outerSetUpRun, isTrue);
+ expect(middleSetUpRun, isTrue);
+ innerSetUpRun = true;
+ }, max: 1));
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(outerSetUpRun, isTrue);
+ expect(middleSetUpRun, isTrue);
+ expect(innerSetUpRun, isTrue);
+ }, max: 1));
+ });
+ });
+ });
+
+ var middleGroup = entries.single as Group;
+ var innerGroup = middleGroup.entries.single as Group;
+ return _runTest(innerGroup.entries.single as Test);
+ });
+
+ test('handles Futures when chained', () {
+ var outerSetUpRun = false;
+ var innerSetUpRun = false;
+ var entries = declare(() {
+ setUp(expectAsync0(() {
+ expect(innerSetUpRun, isFalse);
+ return Future(() => outerSetUpRun = true);
+ }, max: 1));
+
+ group('inner', () {
+ setUp(expectAsync0(() {
+ expect(outerSetUpRun, isTrue);
+ return Future(() => innerSetUpRun = true);
+ }, max: 1));
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(outerSetUpRun, isTrue);
+ expect(innerSetUpRun, isTrue);
+ }, max: 1));
+ });
+ });
+
+ var innerGroup = entries.single as Group;
+ return _runTest(innerGroup.entries.single as Test);
+ });
+
+ test("inherits group's tags", () {
+ var tests = declare(() {
+ group('outer', () {
+ group('inner', () {
+ test('with tags', () {}, tags: 'd');
+ }, tags: ['b', 'c']);
+ }, tags: 'a');
+ });
+
+ var outerGroup = tests.single as Group;
+ var innerGroup = outerGroup.entries.single as Group;
+ var testWithTags = innerGroup.entries.single;
+ expect(outerGroup.metadata.tags, unorderedEquals(['a']));
+ expect(innerGroup.metadata.tags, unorderedEquals(['a', 'b', 'c']));
+ expect(
+ testWithTags.metadata.tags, unorderedEquals(['a', 'b', 'c', 'd']));
+ });
+
+ test('throws on invalid tags', () {
+ expect(() {
+ declare(() {
+ group('a', () {}, tags: 1);
+ });
+ }, throwsArgumentError);
+ });
+ });
+
+ group('.tearDown()', () {
+ test('is scoped to the group', () async {
+ late bool tearDownRun;
+ var entries = declare(() {
+ setUp(() => tearDownRun = false);
+
+ group('group', () {
+ tearDown(() => tearDownRun = true);
+
+ test(
+ 'description 1',
+ expectAsync0(() {
+ expect(tearDownRun, isFalse);
+ }, max: 1));
+ });
+
+ test(
+ 'description 2',
+ expectAsync0(() {
+ expect(tearDownRun, isFalse);
+ }, max: 1));
+ });
+
+ var testGroup = entries[0] as Group;
+ await _runTest(testGroup.entries.single as Test);
+ expect(tearDownRun, isTrue);
+ await _runTest(entries[1] as Test);
+ expect(tearDownRun, isFalse);
+ });
+
+ test('runs from the inside out', () async {
+ var innerTearDownRun = false;
+ var middleTearDownRun = false;
+ var outerTearDownRun = false;
+ var entries = declare(() {
+ tearDown(expectAsync0(() {
+ expect(innerTearDownRun, isTrue);
+ expect(middleTearDownRun, isTrue);
+ outerTearDownRun = true;
+ }, max: 1));
+
+ group('middle', () {
+ tearDown(expectAsync0(() {
+ expect(innerTearDownRun, isTrue);
+ expect(outerTearDownRun, isFalse);
+ middleTearDownRun = true;
+ }, max: 1));
+
+ group('inner', () {
+ tearDown(expectAsync0(() {
+ expect(outerTearDownRun, isFalse);
+ expect(middleTearDownRun, isFalse);
+ innerTearDownRun = true;
+ }, max: 1));
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(outerTearDownRun, isFalse);
+ expect(middleTearDownRun, isFalse);
+ expect(innerTearDownRun, isFalse);
+ }, max: 1));
+ });
+ });
+ });
+
+ var middleGroup = entries.single as Group;
+ var innerGroup = middleGroup.entries.single as Group;
+ await _runTest(innerGroup.entries.single as Test);
+ expect(innerTearDownRun, isTrue);
+ expect(middleTearDownRun, isTrue);
+ expect(outerTearDownRun, isTrue);
+ });
+
+ test('handles Futures when chained', () async {
+ var outerTearDownRun = false;
+ var innerTearDownRun = false;
+ var entries = declare(() {
+ tearDown(expectAsync0(() {
+ expect(innerTearDownRun, isTrue);
+ return Future(() => outerTearDownRun = true);
+ }, max: 1));
+
+ group('inner', () {
+ tearDown(expectAsync0(() {
+ expect(outerTearDownRun, isFalse);
+ return Future(() => innerTearDownRun = true);
+ }, max: 1));
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(outerTearDownRun, isFalse);
+ expect(innerTearDownRun, isFalse);
+ }, max: 1));
+ });
+ });
+
+ var innerGroup = entries.single as Group;
+ await _runTest(innerGroup.entries.single as Test);
+ expect(innerTearDownRun, isTrue);
+ expect(outerTearDownRun, isTrue);
+ });
+
+ test('runs outer callbacks even when inner ones fail', () async {
+ var outerTearDownRun = false;
+ var entries = declare(() {
+ tearDown(() {
+ return Future(() => outerTearDownRun = true);
+ });
+
+ group('inner', () {
+ tearDown(() {
+ throw 'inner error';
+ });
+
+ test(
+ 'description',
+ expectAsync0(() {
+ expect(outerTearDownRun, isFalse);
+ }, max: 1));
+ });
+ });
+
+ var innerGroup = entries.single as Group;
+ await _runTest(innerGroup.entries.single as Test, shouldFail: true);
+ expect(outerTearDownRun, isTrue);
+ });
+ });
+ });
+
+ group('duplicate names', () {
+ test('can be enabled', () {
+ expect(
+ () => declare(() {
+ test('a', expectAsync0(() {}, count: 0));
+ test('a', expectAsync0(() {}, count: 0));
+ }, allowDuplicateTestNames: false),
+ throwsA(isA<DuplicateTestNameException>()
+ .having((e) => e.name, 'name', 'a')));
+ });
+
+ test('are allowed by default', () {
+ expect(
+ declare(() {
+ test('a', expectAsync0(() {}, count: 0));
+ test('a', expectAsync0(() {}, count: 0));
+ }).map((e) => e.name),
+ equals(['a', 'a']));
+ });
+ });
+}
+
+/// Runs [test].
+///
+/// This automatically sets up an `onError` listener to ensure that the test
+/// doesn't throw any invisible exceptions.
+Future _runTest(Test test, {bool shouldFail = false}) {
+ var liveTest = test.load(_suite);
+
+ if (shouldFail) {
+ liveTest.onError.listen(expectAsync1((_) {}));
+ } else {
+ liveTest.onError.listen((e) => registerException(e.error, e.stackTrace));
+ }
+
+ return liveTest.run();
+}
diff --git a/pkgs/test_api/test/backend/invoker_test.dart b/pkgs/test_api/test/backend/invoker_test.dart
new file mode 100644
index 0000000..ef08b38
--- /dev/null
+++ b/pkgs/test_api/test/backend/invoker_test.dart
@@ -0,0 +1,581 @@
+// Copyright (c) 2015, 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 'package:test/test.dart';
+import 'package:test_api/src/backend/group.dart';
+import 'package:test_api/src/backend/invoker.dart';
+import 'package:test_api/src/backend/live_test.dart';
+import 'package:test_api/src/backend/message.dart';
+import 'package:test_api/src/backend/metadata.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_api/src/backend/suite.dart';
+
+import '../utils.dart';
+
+void main() {
+ late Suite suite;
+ setUp(() {
+ lastState = null;
+ suite = Suite(Group.root([]), suitePlatform, ignoreTimeouts: false);
+ });
+
+ group('Invoker.current', () {
+ var invoker = Invoker.current;
+ test('returns null outside of a test body', () {
+ expect(invoker, isNull);
+ });
+
+ test('returns the current invoker in a test body', () async {
+ late Invoker invoker;
+ var liveTest = _localTest(() {
+ invoker = Invoker.current!;
+ }).load(suite);
+ liveTest.onError.listen(expectAsync1((_) {}, count: 0));
+
+ await liveTest.run();
+ expect(invoker.liveTest, equals(liveTest));
+ });
+
+ test('returns the current invoker in a test body after the test completes',
+ () async {
+ Status? status;
+ var completer = Completer<Invoker>();
+ var liveTest = _localTest(() {
+ // Use the event loop to wait longer than a microtask for the test to
+ // complete.
+ Future(() {
+ status = Invoker.current!.liveTest.state.status;
+ completer.complete(Invoker.current);
+ });
+ }).load(suite);
+ liveTest.onError.listen(expectAsync1((_) {}, count: 0));
+
+ expect(liveTest.run(), completes);
+ var invoker = await completer.future;
+ expect(invoker.liveTest, equals(liveTest));
+ expect(status, equals(Status.complete));
+ });
+ });
+
+ group('in a successful test,', () {
+ test('the state changes from pending to running to complete', () async {
+ late State stateInTest;
+ late LiveTest liveTest;
+ liveTest = _localTest(() {
+ stateInTest = liveTest.state;
+ }).load(suite);
+ liveTest.onError.listen(expectAsync1((_) {}, count: 0));
+
+ expect(liveTest.state.status, equals(Status.pending));
+ expect(liveTest.state.result, equals(Result.success));
+
+ var future = liveTest.run();
+
+ expect(liveTest.state.status, equals(Status.running));
+ expect(liveTest.state.result, equals(Result.success));
+
+ await future;
+
+ expect(stateInTest.status, equals(Status.running));
+ expect(stateInTest.result, equals(Result.success));
+
+ expect(liveTest.state.status, equals(Status.complete));
+ expect(liveTest.state.result, equals(Result.success));
+ });
+
+ test('onStateChange fires for each state change', () {
+ var liveTest = _localTest(() {}).load(suite);
+ liveTest.onError.listen(expectAsync1((_) {}, count: 0));
+
+ var first = true;
+ liveTest.onStateChange.listen(expectAsync1((state) {
+ if (first) {
+ expect(state.status, equals(Status.running));
+ first = false;
+ } else {
+ expect(state.status, equals(Status.complete));
+ }
+ expect(state.result, equals(Result.success));
+ }, count: 2, max: 2));
+
+ return liveTest.run();
+ });
+
+ test('onComplete completes once the test body is done', () {
+ var testRun = false;
+ var liveTest = _localTest(() {
+ testRun = true;
+ }).load(suite);
+
+ expect(liveTest.onComplete.then((_) {
+ expect(testRun, isTrue);
+ }), completes);
+
+ return liveTest.run();
+ });
+ });
+
+ group('in a test with failures,', () {
+ test('a synchronous throw is reported and causes the test to fail', () {
+ var liveTest = _localTest(() {
+ throw TestFailure('oh no');
+ }).load(suite);
+
+ expectSingleFailure(liveTest);
+ return liveTest.run();
+ });
+
+ test('a synchronous reported failure causes the test to fail', () {
+ var liveTest = _localTest(() {
+ registerException(TestFailure('oh no'));
+ }).load(suite);
+
+ expectSingleFailure(liveTest);
+ return liveTest.run();
+ });
+
+ test('a failure reported asynchronously during the test causes it to fail',
+ () {
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+ Future(() => registerException(TestFailure('oh no')))
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ }).load(suite);
+
+ expectSingleFailure(liveTest);
+ return liveTest.run();
+ });
+
+ test('a failure thrown asynchronously during the test causes it to fail',
+ () {
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+ Future(() => throw TestFailure('oh no'))
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ }).load(suite);
+
+ expectSingleFailure(liveTest);
+ return liveTest.run();
+ });
+
+ test('a failure reported asynchronously after the test causes it to error',
+ () {
+ var liveTest = _localTest(() {
+ Future(() => registerException(TestFailure('oh no')));
+ }).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.success),
+ const State(Status.complete, Result.failure),
+ const State(Status.complete, Result.error)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(
+ lastState, equals(const State(Status.complete, Result.failure)));
+ expect(error, isTestFailure('oh no'));
+ },
+ (error) {
+ expect(lastState, equals(const State(Status.complete, Result.error)));
+ expect(
+ error,
+ equals('This test failed after it had already completed.\n'
+ 'Make sure to use a matching library which informs the '
+ 'test runner\nof pending async work.'));
+ }
+ ]);
+
+ return liveTest.run();
+ });
+
+ test('multiple asynchronous failures are reported', () {
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+ Future(() => throw TestFailure('one'));
+ Future(() => throw TestFailure('two'));
+ Future(() => throw TestFailure('three'));
+ Future(() => throw TestFailure('four'))
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ }).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.failure)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(lastState?.status, equals(Status.complete));
+ expect(error, isTestFailure('one'));
+ },
+ (error) {
+ expect(error, isTestFailure('two'));
+ },
+ (error) {
+ expect(error, isTestFailure('three'));
+ },
+ (error) {
+ expect(error, isTestFailure('four'));
+ }
+ ]);
+
+ return liveTest.run();
+ });
+
+ test("a failure after an error doesn't change the state of the test", () {
+ var liveTest = _localTest(() {
+ Future(() => throw TestFailure('fail'));
+ throw 'error';
+ }).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.error)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(lastState, equals(const State(Status.complete, Result.error)));
+ expect(error, equals('error'));
+ },
+ (error) {
+ expect(error, isTestFailure('fail'));
+ }
+ ]);
+
+ return liveTest.run();
+ });
+ });
+
+ group('in a test with errors,', () {
+ test('a synchronous throw is reported and causes the test to error', () {
+ var liveTest = _localTest(() {
+ throw 'oh no';
+ }).load(suite);
+
+ expectSingleError(liveTest);
+ return liveTest.run();
+ });
+
+ test('a synchronous reported error causes the test to error', () {
+ var liveTest = _localTest(() {
+ registerException('oh no');
+ }).load(suite);
+
+ expectSingleError(liveTest);
+ return liveTest.run();
+ });
+
+ test('an error reported asynchronously during the test causes it to error',
+ () {
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+ Future(() => registerException('oh no'))
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ }).load(suite);
+
+ expectSingleError(liveTest);
+ return liveTest.run();
+ });
+
+ test('an error thrown asynchronously during the test causes it to error',
+ () {
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+ Future(() => throw 'oh no')
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ }).load(suite);
+
+ expectSingleError(liveTest);
+ return liveTest.run();
+ });
+
+ test('an error reported asynchronously after the test causes it to error',
+ () {
+ var liveTest = _localTest(() {
+ Future(() => registerException('oh no'));
+ }).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.success),
+ const State(Status.complete, Result.error)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(lastState, equals(const State(Status.complete, Result.error)));
+ expect(error, equals('oh no'));
+ },
+ (error) {
+ expect(
+ error,
+ equals('This test failed after it had already completed.\n'
+ 'Make sure to use a matching library which informs the '
+ 'test runner\nof pending async work.'));
+ }
+ ]);
+
+ return liveTest.run();
+ });
+
+ test('multiple asynchronous errors are reported', () {
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+ Future(() => throw 'one');
+ Future(() => throw 'two');
+ Future(() => throw 'three');
+ Future(() => throw 'four')
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ }).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.error)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(lastState?.status, equals(Status.complete));
+ expect(error, equals('one'));
+ },
+ (error) {
+ expect(error, equals('two'));
+ },
+ (error) {
+ expect(error, equals('three'));
+ },
+ (error) {
+ expect(error, equals('four'));
+ }
+ ]);
+
+ return liveTest.run();
+ });
+
+ test('an error after a failure changes the state of the test', () {
+ var liveTest = _localTest(() {
+ Future(() => throw 'error');
+ throw TestFailure('fail');
+ }).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.failure),
+ const State(Status.complete, Result.error)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(
+ lastState, equals(const State(Status.complete, Result.failure)));
+ expect(error, isTestFailure('fail'));
+ },
+ (error) {
+ expect(lastState, equals(const State(Status.complete, Result.error)));
+ expect(error, equals('error'));
+ }
+ ]);
+
+ return liveTest.run();
+ });
+ });
+
+ test("a test doesn't complete until there are no outstanding callbacks",
+ () async {
+ var outstandingCallbackRemoved = false;
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+
+ // Pump the event queue to make sure the test isn't coincidentally
+ // completing after the outstanding callback is removed.
+ pumpEventQueue().then((_) {
+ outstandingCallbackRemoved = true;
+ Invoker.current!.removeOutstandingCallback();
+ });
+ }).load(suite);
+
+ liveTest.onError.listen(expectAsync1((_) {}, count: 0));
+
+ await liveTest.run();
+ expect(outstandingCallbackRemoved, isTrue);
+ });
+
+ test("a test's prints are captured and reported", () {
+ expect(() {
+ var liveTest = _localTest(() {
+ print('Hello,');
+ return Future(() => print('world!'));
+ }).load(suite);
+
+ expect(
+ liveTest.onMessage.take(2).toList().then((messages) {
+ expect(messages[0].type, equals(MessageType.print));
+ expect(messages[0].text, equals('Hello,'));
+ expect(messages[1].type, equals(MessageType.print));
+ expect(messages[1].text, equals('world!'));
+ }),
+ completes);
+
+ return liveTest.run();
+ }, prints(isEmpty));
+ });
+
+ group('timeout:', () {
+ test('A test can be timed out', () {
+ var liveTest = _localTest(() {
+ Invoker.current!.addOutstandingCallback();
+ },
+ metadata: Metadata(
+ chainStackTraces: true,
+ timeout: const Timeout(Duration.zero)))
+ .load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.error)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(lastState!.status, equals(Status.complete));
+ expect(error, const TypeMatcher<TimeoutException>());
+ }
+ ]);
+
+ liveTest.run();
+ });
+
+ test('can be ignored', () {
+ suite = Suite(Group.root([]), suitePlatform, ignoreTimeouts: true);
+ var liveTest = _localTest(() async {
+ await Future<void>.delayed(const Duration(milliseconds: 10));
+ },
+ metadata: Metadata(
+ chainStackTraces: true,
+ timeout: const Timeout(Duration.zero)))
+ .load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.success)
+ ]);
+
+ liveTest.run();
+ });
+ });
+
+ group('runTearDowns', () {
+ test('runs multiple tear downs', () async {
+ var firstTearDownStarted = false;
+ var secondTearDownStarted = false;
+ await Invoker.current!.runTearDowns([
+ () {
+ firstTearDownStarted = true;
+ },
+ () {
+ secondTearDownStarted = true;
+ }
+ ]);
+ expect(secondTearDownStarted, isTrue);
+ expect(firstTearDownStarted, isTrue);
+ });
+
+ test('waits for the future returned tear downs to complete', () async {
+ var firstTearDownWork = Completer<void>();
+ var secondTearDownStarted = false;
+ var result = Invoker.current!.runTearDowns([
+ () {
+ secondTearDownStarted = true;
+ },
+ () async {
+ await firstTearDownWork.future;
+ },
+ ]);
+ await pumpEventQueue();
+ expect(secondTearDownStarted, isFalse);
+ firstTearDownWork.complete();
+ await result;
+ expect(secondTearDownStarted, isTrue);
+ });
+
+ test('allows next tear down to run while there are still prior callbacks',
+ () async {
+ var firstTearDownAsyncWork = Completer<void>();
+ var secondTearDownStarted = false;
+ unawaited(Invoker.current!.runTearDowns([
+ () {
+ secondTearDownStarted = true;
+ },
+ () {
+ Invoker.current!.addOutstandingCallback();
+ firstTearDownAsyncWork.future
+ .whenComplete(Invoker.current!.removeOutstandingCallback);
+ },
+ ]));
+ await pumpEventQueue();
+ expect(secondTearDownStarted, isTrue);
+ firstTearDownAsyncWork.complete();
+ });
+
+ test('forwards errors to the enclosing test but does not end it', () async {
+ var liveTest = _localTest(() async {
+ Invoker.current!.addOutstandingCallback();
+ await Invoker.current!.runTearDowns([
+ () {
+ throw 'oh no';
+ }
+ ]);
+ }).load(suite);
+
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.error)
+ ]);
+
+ var isComplete = false;
+ unawaited(liveTest.run().then((_) => isComplete = true));
+ await pumpEventQueue();
+ expect(liveTest.state.status, equals(Status.complete));
+ expect(isComplete, isFalse);
+ });
+ });
+
+ group('printOnFailure:', () {
+ test("doesn't print anything if the test succeeds", () async {
+ var liveTest = _localTest(() {
+ Invoker.current!.printOnFailure('only on failure');
+ }).load(suite);
+ liveTest.onError.listen(expectAsync1((_) {}, count: 0));
+
+ liveTest.onMessage.listen(expectAsync1((_) {}, count: 0));
+
+ await liveTest.run();
+ });
+
+ test('prints if the test fails', () async {
+ var liveTest = _localTest(() {
+ Invoker.current!.printOnFailure('only on failure');
+ expect(true, isFalse);
+ }).load(suite);
+ liveTest.onError.listen(expectAsync1((_) {}, count: 1));
+
+ liveTest.onMessage.listen(expectAsync1((message) {
+ expect(message.type, equals(MessageType.print));
+ expect(message.text, equals('only on failure'));
+ }, count: 1));
+
+ await liveTest.run();
+ });
+ });
+}
+
+LocalTest _localTest(dynamic Function() body, {Metadata? metadata}) {
+ metadata ??= Metadata(chainStackTraces: true);
+ return LocalTest('test', metadata, body);
+}
diff --git a/pkgs/test_api/test/backend/metadata_test.dart b/pkgs/test_api/test/backend/metadata_test.dart
new file mode 100644
index 0000000..f42a535
--- /dev/null
+++ b/pkgs/test_api/test/backend/metadata_test.dart
@@ -0,0 +1,257 @@
+// Copyright (c) 2015, 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 'package:boolean_selector/boolean_selector.dart';
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/metadata.dart';
+import 'package:test_api/src/backend/platform_selector.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_api/src/backend/suite_platform.dart';
+
+void main() {
+ group('tags', () {
+ test('parses an Iterable', () {
+ expect(
+ Metadata.parse(tags: ['a', 'b']).tags, unorderedEquals(['a', 'b']));
+ });
+
+ test('parses a String', () {
+ expect(Metadata.parse(tags: 'a').tags, unorderedEquals(['a']));
+ });
+
+ test('parses null', () {
+ expect(Metadata.parse().tags, unorderedEquals([]));
+ });
+
+ test('parse refuses an invalid type', () {
+ expect(() => Metadata.parse(tags: 1), throwsArgumentError);
+ });
+
+ test('parse refuses an invalid type in a list', () {
+ expect(() => Metadata.parse(tags: [1]), throwsArgumentError);
+ });
+
+ test('merges tags by computing the union of the two tag sets', () {
+ var merged = Metadata(tags: ['a', 'b']).merge(Metadata(tags: ['b', 'c']));
+ expect(merged.tags, unorderedEquals(['a', 'b', 'c']));
+ });
+
+ test('serializes and deserializes tags', () {
+ var metadata = Metadata(tags: ['a', 'b']).serialize();
+ expect(Metadata.deserialize(metadata).tags, unorderedEquals(['a', 'b']));
+ });
+ });
+
+ group('constructor', () {
+ test("returns the normal metadata if there's no forTag", () {
+ var metadata = Metadata(verboseTrace: true, tags: ['foo', 'bar']);
+ expect(metadata.verboseTrace, isTrue);
+ expect(metadata.tags, equals(['foo', 'bar']));
+ });
+
+ test("returns the normal metadata if there's no tags", () {
+ var metadata = Metadata(
+ verboseTrace: true,
+ forTag: {BooleanSelector.parse('foo'): Metadata(skip: true)});
+ expect(metadata.verboseTrace, isTrue);
+ expect(metadata.skip, isFalse);
+ expect(metadata.forTag, contains(BooleanSelector.parse('foo')));
+ expect(metadata.forTag[BooleanSelector.parse('foo')]?.skip, isTrue);
+ });
+
+ test("returns the normal metadata if forTag doesn't match tags", () {
+ var metadata = Metadata(
+ verboseTrace: true,
+ tags: ['bar', 'baz'],
+ forTag: {BooleanSelector.parse('foo'): Metadata(skip: true)});
+
+ expect(metadata.verboseTrace, isTrue);
+ expect(metadata.skip, isFalse);
+ expect(metadata.tags, unorderedEquals(['bar', 'baz']));
+ expect(metadata.forTag, contains(BooleanSelector.parse('foo')));
+ expect(metadata.forTag[BooleanSelector.parse('foo')]?.skip, isTrue);
+ });
+
+ test('resolves forTags that match tags', () {
+ var metadata = Metadata(verboseTrace: true, tags: [
+ 'foo',
+ 'bar',
+ 'baz'
+ ], forTag: {
+ BooleanSelector.parse('foo'): Metadata(skip: true),
+ BooleanSelector.parse('baz'): Metadata(timeout: Timeout.none),
+ BooleanSelector.parse('qux'): Metadata(skipReason: 'blah')
+ });
+
+ expect(metadata.verboseTrace, isTrue);
+ expect(metadata.skip, isTrue);
+ expect(metadata.skipReason, isNull);
+ expect(metadata.timeout, equals(Timeout.none));
+ expect(metadata.tags, unorderedEquals(['foo', 'bar', 'baz']));
+ expect(metadata.forTag.keys, equals([BooleanSelector.parse('qux')]));
+ });
+
+ test('resolves forTags that adds a behavioral tag', () {
+ var metadata = Metadata(tags: [
+ 'foo'
+ ], forTag: {
+ BooleanSelector.parse('baz'): Metadata(skip: true),
+ BooleanSelector.parse('bar'):
+ Metadata(verboseTrace: true, tags: ['baz']),
+ BooleanSelector.parse('foo'): Metadata(tags: ['bar'])
+ });
+
+ expect(metadata.verboseTrace, isTrue);
+ expect(metadata.skip, isTrue);
+ expect(metadata.tags, unorderedEquals(['foo', 'bar', 'baz']));
+ expect(metadata.forTag, isEmpty);
+ });
+
+ test('resolves forTags that adds circular tags', () {
+ var metadata = Metadata(tags: [
+ 'foo'
+ ], forTag: {
+ BooleanSelector.parse('foo'): Metadata(tags: ['bar']),
+ BooleanSelector.parse('bar'): Metadata(tags: ['baz']),
+ BooleanSelector.parse('baz'): Metadata(tags: ['foo'])
+ });
+
+ expect(metadata.tags, unorderedEquals(['foo', 'bar', 'baz']));
+ expect(metadata.forTag, isEmpty);
+ });
+
+ test('base metadata takes precedence over forTags', () {
+ var metadata = Metadata(verboseTrace: true, tags: [
+ 'foo'
+ ], forTag: {
+ BooleanSelector.parse('foo'): Metadata(verboseTrace: false)
+ });
+
+ expect(metadata.verboseTrace, isTrue);
+ });
+ });
+
+ group('onPlatform', () {
+ test('parses a valid map', () {
+ var metadata = Metadata.parse(onPlatform: {
+ 'chrome': const Timeout.factor(2),
+ 'vm': [const Skip(), const Timeout.factor(3)]
+ });
+
+ var key = metadata.onPlatform.keys.first;
+ expect(
+ key.evaluate(SuitePlatform(Runtime.chrome, compiler: null)), isTrue);
+ expect(key.evaluate(SuitePlatform(Runtime.vm, compiler: null)), isFalse);
+ var value = metadata.onPlatform.values.first;
+ expect(value.timeout.scaleFactor, equals(2));
+
+ key = metadata.onPlatform.keys.last;
+ expect(key.evaluate(SuitePlatform(Runtime.vm, compiler: null)), isTrue);
+ expect(
+ key.evaluate(SuitePlatform(Runtime.chrome, compiler: null)), isFalse);
+ value = metadata.onPlatform.values.last;
+ expect(value.skip, isTrue);
+ expect(value.timeout.scaleFactor, equals(3));
+ });
+
+ test('refuses an invalid value', () {
+ expect(() {
+ Metadata.parse(onPlatform: {'chrome': const TestOn('chrome')});
+ }, throwsArgumentError);
+ });
+
+ test('refuses an invalid value in a list', () {
+ expect(() {
+ Metadata.parse(onPlatform: {
+ 'chrome': [const TestOn('chrome')]
+ });
+ }, throwsArgumentError);
+ });
+
+ test('refuses an invalid platform selector', () {
+ expect(() {
+ Metadata.parse(onPlatform: {'vm &&': const Skip()});
+ }, throwsFormatException);
+ });
+
+ test('refuses multiple Timeouts', () {
+ expect(() {
+ Metadata.parse(onPlatform: {
+ 'chrome': [const Timeout.factor(2), const Timeout.factor(3)]
+ });
+ }, throwsArgumentError);
+ });
+
+ test('refuses multiple Skips', () {
+ expect(() {
+ Metadata.parse(onPlatform: {
+ 'chrome': [const Skip(), const Skip()]
+ });
+ }, throwsArgumentError);
+ });
+ });
+
+ group('validatePlatformSelectors', () {
+ test('succeeds if onPlatform uses valid platforms', () {
+ Metadata.parse(onPlatform: {'vm || browser': const Skip()})
+ .validatePlatformSelectors({'vm'});
+ });
+
+ test('succeeds if testOn uses valid platforms', () {
+ Metadata.parse(testOn: 'vm || browser').validatePlatformSelectors({'vm'});
+ });
+
+ test('succeeds if testOn uses valid compilers', () {
+ Metadata.parse(testOn: 'dart2js || kernel').validatePlatformSelectors({});
+ });
+
+ test('fails if onPlatform uses an invalid platform', () {
+ expect(() {
+ Metadata.parse(onPlatform: {'unknown': const Skip()})
+ .validatePlatformSelectors({'vm'});
+ }, throwsFormatException);
+ });
+
+ test('fails if testOn uses an invalid platform', () {
+ expect(() {
+ Metadata.parse(testOn: 'unknown').validatePlatformSelectors({'vm'});
+ }, throwsFormatException);
+ });
+
+ test('fails if testOn uses an invalid compiler', () {
+ expect(() {
+ Metadata.parse(testOn: 'foo2bar').validatePlatformSelectors({});
+ }, throwsFormatException);
+ });
+ });
+
+ group('change', () {
+ test('preserves all fields if no parameters are passed', () {
+ var metadata = Metadata(
+ testOn: PlatformSelector.parse('linux'),
+ timeout: const Timeout.factor(2),
+ skip: true,
+ skipReason: 'just because',
+ verboseTrace: true,
+ tags: [
+ 'foo',
+ 'bar'
+ ],
+ onPlatform: {
+ PlatformSelector.parse('mac-os'): Metadata(skip: false)
+ },
+ forTag: {
+ BooleanSelector.parse('slow'):
+ Metadata(timeout: const Timeout.factor(4))
+ });
+ expect(metadata.serialize(), equals(metadata.change().serialize()));
+ });
+
+ test('updates a changed field', () {
+ var metadata = Metadata(timeout: const Timeout.factor(2));
+ expect(metadata.change(timeout: const Timeout.factor(3)).timeout,
+ equals(const Timeout.factor(3)));
+ });
+ });
+}
diff --git a/pkgs/test_api/test/frontend/add_tear_down_test.dart b/pkgs/test_api/test/frontend/add_tear_down_test.dart
new file mode 100644
index 0000000..de4bb55
--- /dev/null
+++ b/pkgs/test_api/test/frontend/add_tear_down_test.dart
@@ -0,0 +1,798 @@
+// Copyright (c) 2016, 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 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void main() {
+ group('in a test', () {
+ test('runs after the test body', () {
+ return expectTestsPass(() {
+ var test1Run = false;
+ var tearDownRun = false;
+ test('test 1', () {
+ addTearDown(() {
+ expect(test1Run, isTrue);
+ expect(tearDownRun, isFalse);
+ tearDownRun = true;
+ });
+
+ expect(tearDownRun, isFalse);
+ test1Run = true;
+ });
+
+ test('test 2', () {
+ expect(tearDownRun, isTrue);
+ });
+ });
+ });
+
+ test('multiples run in reverse order', () {
+ return expectTestsPass(() {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+
+ test('test 1', () {
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ tearDown1Run = true;
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isTrue);
+ tearDown2Run = true;
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ tearDown3Run = true;
+ });
+
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+
+ test('test 2', () {
+ expect(tearDown1Run, isTrue);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+ });
+ });
+
+ test('can be called in addTearDown', () {
+ return expectTestsPass(() {
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+
+ test('test 1', () {
+ addTearDown(() {
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isFalse);
+ tearDown3Run = true;
+ });
+
+ addTearDown(() {
+ addTearDown(() {
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ tearDown2Run = true;
+ });
+ });
+ });
+
+ test('test 2', () {
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+ });
+ });
+
+ test('can be called in tearDown', () {
+ return expectTestsPass(() {
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+
+ tearDown(() {
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isFalse);
+ tearDown3Run = true;
+ });
+
+ tearDown(() {
+ tearDown2Run = false;
+ tearDown3Run = false;
+
+ addTearDown(() {
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ tearDown2Run = true;
+ });
+ });
+
+ test('test 1', () {});
+
+ test('test 2', () {
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+ });
+ });
+
+ test('runs before a normal tearDown', () {
+ return expectTestsPass(() {
+ var groupTearDownRun = false;
+ var testTearDownRun = false;
+ group('group', () {
+ tearDown(() {
+ expect(testTearDownRun, isTrue);
+ expect(groupTearDownRun, isFalse);
+ groupTearDownRun = true;
+ });
+
+ test('test 1', () {
+ addTearDown(() {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ testTearDownRun = true;
+ });
+
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ });
+ });
+
+ test('test 2', () {
+ expect(groupTearDownRun, isTrue);
+ expect(testTearDownRun, isTrue);
+ });
+ });
+ });
+
+ test('runs in the same error zone as the test', () {
+ return expectTestsPass(() {
+ test('test', () {
+ final testBodyZone = Zone.current;
+
+ addTearDown(() {
+ final tearDownZone = Zone.current;
+ expect(tearDownZone.inSameErrorZone(testBodyZone), isTrue,
+ reason: 'The tear down callback is in a different error zone '
+ 'than the test body.');
+ });
+ });
+ });
+ });
+
+ group('asynchronously', () {
+ test('blocks additional test tearDowns on in-band async', () {
+ return expectTestsPass(() {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+ test('test', () {
+ addTearDown(() async {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ await pumpEventQueue();
+ tearDown1Run = true;
+ });
+
+ addTearDown(() async {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isTrue);
+ await pumpEventQueue();
+ tearDown2Run = true;
+ });
+
+ addTearDown(() async {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ await pumpEventQueue();
+ tearDown3Run = true;
+ });
+
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+ });
+ });
+
+ test("doesn't block additional test tearDowns on out-of-band async", () {
+ return expectTestsPass(() {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+ test('test', () {
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+
+ expect(Future(() {
+ tearDown1Run = true;
+ }), completes);
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+
+ expect(Future(() {
+ tearDown2Run = true;
+ }), completes);
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+
+ expect(Future(() {
+ tearDown3Run = true;
+ }), completes);
+ });
+
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+ });
+ });
+
+ test('blocks additional group tearDowns on in-band async', () {
+ return expectTestsPass(() {
+ var groupTearDownRun = false;
+ var testTearDownRun = false;
+ tearDown(() async {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isTrue);
+ await pumpEventQueue();
+ groupTearDownRun = true;
+ });
+
+ test('test', () {
+ addTearDown(() async {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ await pumpEventQueue();
+ testTearDownRun = true;
+ });
+
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ });
+ });
+ });
+
+ test("doesn't block additional group tearDowns on out-of-band async", () {
+ return expectTestsPass(() {
+ var groupTearDownRun = false;
+ var testTearDownRun = false;
+ tearDown(() {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+
+ expect(Future(() {
+ groupTearDownRun = true;
+ }), completes);
+ });
+
+ test('test', () {
+ addTearDown(() {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+
+ expect(Future(() {
+ testTearDownRun = true;
+ }), completes);
+ });
+
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ });
+ });
+ });
+
+ test('blocks further tests on in-band async', () {
+ return expectTestsPass(() {
+ var tearDownRun = false;
+ test('test 1', () {
+ addTearDown(() async {
+ expect(tearDownRun, isFalse);
+ await pumpEventQueue();
+ tearDownRun = true;
+ });
+ });
+
+ test('test 2', () {
+ expect(tearDownRun, isTrue);
+ });
+ });
+ });
+
+ test('blocks further tests on out-of-band async', () {
+ return expectTestsPass(() {
+ var tearDownRun = false;
+ test('test 1', () {
+ addTearDown(() async {
+ expect(tearDownRun, isFalse);
+ expect(
+ pumpEventQueue().then((_) {
+ tearDownRun = true;
+ }),
+ completes);
+ });
+ });
+
+ test('after', () {
+ expect(tearDownRun, isTrue);
+ });
+ });
+ });
+ });
+
+ group('with an error', () {
+ test('reports the error', () async {
+ var engine = declareEngine(() {
+ test('test', () {
+ addTearDown(() => throw TestFailure('fail'));
+ });
+ });
+
+ var queue = StreamQueue(engine.onTestStarted);
+ var liveTestFuture = queue.next;
+
+ expect(await engine.run(), isFalse);
+
+ var liveTest = await liveTestFuture;
+ expect(liveTest.test.name, equals('test'));
+ expectTestFailed(liveTest, 'fail');
+ });
+
+ test('runs further test tearDowns', () async {
+ // Declare this in the outer test so if it doesn't run, the outer test
+ // will fail.
+ var shouldRun = expectAsync0(() {});
+
+ var engine = declareEngine(() {
+ test('test', () {
+ addTearDown(() => throw 'error');
+ addTearDown(shouldRun);
+ });
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test('runs further group tearDowns', () async {
+ // Declare this in the outer test so if it doesn't run, the outer test
+ // will fail.
+ var shouldRun = expectAsync0(() {});
+
+ var engine = declareEngine(() {
+ tearDown(shouldRun);
+
+ test('test', () {
+ addTearDown(() => throw 'error');
+ });
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+ });
+ });
+
+ group('in setUpAll()', () {
+ test('runs after all tests', () async {
+ var test1Run = false;
+ var test2Run = false;
+ var tearDownRun = false;
+ await expectTestsPass(() {
+ setUpAll(() {
+ addTearDown(() {
+ expect(test1Run, isTrue);
+ expect(test2Run, isTrue);
+ expect(tearDownRun, isFalse);
+ tearDownRun = true;
+ });
+ });
+
+ test('test 1', () {
+ test1Run = true;
+ expect(tearDownRun, isFalse);
+ });
+
+ test('test 2', () {
+ test2Run = true;
+ expect(tearDownRun, isFalse);
+ });
+ });
+
+ expect(test1Run, isTrue);
+ expect(test2Run, isTrue);
+ expect(tearDownRun, isTrue);
+ });
+
+ test('multiples run in reverse order', () async {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+ await expectTestsPass(() {
+ setUpAll(() {
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ tearDown1Run = true;
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isTrue);
+ tearDown2Run = true;
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ tearDown3Run = true;
+ });
+
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+
+ test('test', () {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+ });
+
+ expect(tearDown1Run, isTrue);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+
+ test('can be called in addTearDown', () async {
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+ await expectTestsPass(() {
+ setUpAll(() {
+ addTearDown(() {
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isFalse);
+ tearDown3Run = true;
+ });
+
+ addTearDown(() {
+ addTearDown(() {
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ tearDown2Run = true;
+ });
+ });
+ });
+
+ test('test', () {
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+ });
+
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+
+ test('can be called in tearDownAll', () async {
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+ await expectTestsPass(() {
+ tearDownAll(() {
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isFalse);
+ tearDown3Run = true;
+ });
+
+ tearDownAll(() {
+ tearDown2Run = false;
+ tearDown3Run = false;
+
+ addTearDown(() {
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ tearDown2Run = true;
+ });
+ });
+
+ test('test', () {});
+ });
+
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+
+ test('runs before a normal tearDownAll', () async {
+ var groupTearDownRun = false;
+ var testTearDownRun = false;
+ await expectTestsPass(() {
+ tearDownAll(() {
+ expect(testTearDownRun, isTrue);
+ expect(groupTearDownRun, isFalse);
+ groupTearDownRun = true;
+ });
+
+ setUpAll(() {
+ addTearDown(() {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ testTearDownRun = true;
+ });
+ });
+
+ test('test', () {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ });
+ });
+
+ expect(groupTearDownRun, isTrue);
+ expect(testTearDownRun, isTrue);
+ });
+
+ test('runs in the same error zone as the setUpAll', () async {
+ return expectTestsPass(() {
+ setUpAll(() {
+ final setUpAllZone = Zone.current;
+
+ addTearDown(() {
+ final tearDownZone = Zone.current;
+ expect(tearDownZone.inSameErrorZone(setUpAllZone), isTrue,
+ reason: 'The tear down callback is in a different error zone '
+ 'than the set up all callback.');
+ });
+ });
+
+ test('test', () {});
+ });
+ });
+
+ group('asynchronously', () {
+ test('blocks additional tearDowns on in-band async', () async {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+ await expectTestsPass(() {
+ setUpAll(() {
+ addTearDown(() async {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ await pumpEventQueue();
+ tearDown1Run = true;
+ });
+
+ addTearDown(() async {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isTrue);
+ await pumpEventQueue();
+ tearDown2Run = true;
+ });
+
+ addTearDown(() async {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ await pumpEventQueue();
+ tearDown3Run = true;
+ });
+ });
+
+ test('test', () {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+ });
+
+ expect(tearDown1Run, isTrue);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+
+ test("doesn't block additional tearDowns on out-of-band async", () async {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDown3Run = false;
+ await expectTestsPass(() {
+ setUpAll(() {
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+
+ expect(Future(() {
+ tearDown1Run = true;
+ }), completes);
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+
+ expect(Future(() {
+ tearDown2Run = true;
+ }), completes);
+ });
+
+ addTearDown(() {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+
+ expect(Future(() {
+ tearDown3Run = true;
+ }), completes);
+ });
+ });
+
+ test('test', () {
+ expect(tearDown1Run, isFalse);
+ expect(tearDown2Run, isFalse);
+ expect(tearDown3Run, isFalse);
+ });
+ });
+
+ expect(tearDown1Run, isTrue);
+ expect(tearDown2Run, isTrue);
+ expect(tearDown3Run, isTrue);
+ });
+
+ test('blocks additional tearDownAlls on in-band async', () async {
+ var groupTearDownRun = false;
+ var testTearDownRun = false;
+ await expectTestsPass(() {
+ tearDownAll(() async {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isTrue);
+ await pumpEventQueue();
+ groupTearDownRun = true;
+ });
+
+ setUpAll(() {
+ addTearDown(() async {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ await pumpEventQueue();
+ testTearDownRun = true;
+ });
+ });
+
+ test('test', () {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ });
+ });
+
+ expect(groupTearDownRun, isTrue);
+ expect(testTearDownRun, isTrue);
+ });
+
+ test("doesn't block additional tearDownAlls on out-of-band async",
+ () async {
+ var groupTearDownRun = false;
+ var testTearDownRun = false;
+ await expectTestsPass(() {
+ tearDownAll(() {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+
+ expect(Future(() {
+ groupTearDownRun = true;
+ }), completes);
+ });
+
+ setUpAll(() {
+ addTearDown(() {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+
+ expect(Future(() {
+ testTearDownRun = true;
+ }), completes);
+ });
+ });
+
+ test('test', () {
+ expect(groupTearDownRun, isFalse);
+ expect(testTearDownRun, isFalse);
+ });
+ });
+
+ expect(groupTearDownRun, isTrue);
+ expect(testTearDownRun, isTrue);
+ });
+ });
+
+ group('with an error', () {
+ test('reports the error', () async {
+ var engine = declareEngine(() {
+ setUpAll(() {
+ addTearDown(() => throw TestFailure('fail'));
+ });
+
+ test('test', () {});
+ });
+
+ var queue = StreamQueue(engine.onTestStarted);
+ unawaited(queue.skip(2));
+ var liveTestFuture = queue.next;
+
+ expect(await engine.run(), isFalse);
+
+ var liveTest = await liveTestFuture;
+ expect(liveTest.test.name, equals('(tearDownAll)'));
+ expectTestFailed(liveTest, 'fail');
+ });
+
+ test('runs further tearDowns', () async {
+ // Declare this in the outer test so if it doesn't run, the outer test
+ // will fail.
+ var shouldRun = expectAsync0(() {});
+
+ var engine = declareEngine(() {
+ setUpAll(() {
+ addTearDown(() => throw 'error');
+ addTearDown(shouldRun);
+ });
+
+ test('test', () {});
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test('runs further tearDownAlls', () async {
+ // Declare this in the outer test so if it doesn't run, the outer test
+ // will fail.
+ var shouldRun = expectAsync0(() {});
+
+ var engine = declareEngine(() {
+ tearDownAll(shouldRun);
+
+ setUpAll(() {
+ addTearDown(() => throw 'error');
+ });
+
+ test('test', () {});
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+ });
+ });
+}
diff --git a/pkgs/test_api/test/frontend/fake_test.dart b/pkgs/test_api/test/frontend/fake_test.dart
new file mode 100644
index 0000000..7dfb306
--- /dev/null
+++ b/pkgs/test_api/test/frontend/fake_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2020, 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 'package:test/test.dart';
+import 'package:test_api/fake.dart' as test_api;
+
+void main() {
+ late _FakeSample fake;
+ setUp(() {
+ fake = _FakeSample();
+ });
+ test('method invocation', () {
+ expect(() => fake.f(), throwsA(const TypeMatcher<UnimplementedError>()));
+ });
+ test('getter', () {
+ expect(() => fake.x, throwsA(const TypeMatcher<UnimplementedError>()));
+ });
+ test('setter', () {
+ expect(() => fake.x = 0, throwsA(const TypeMatcher<UnimplementedError>()));
+ });
+ test('operator', () {
+ expect(() => fake + 1, throwsA(const TypeMatcher<UnimplementedError>()));
+ });
+}
+
+class _Sample {
+ void f() {}
+
+ int get x => 0;
+
+ set x(int value) {}
+
+ int operator +(int other) => 0;
+}
+
+class _FakeSample extends test_api.Fake implements _Sample {}
diff --git a/pkgs/test_api/test/frontend/set_up_all_test.dart b/pkgs/test_api/test/frontend/set_up_all_test.dart
new file mode 100644
index 0000000..60d4dc8
--- /dev/null
+++ b/pkgs/test_api/test/frontend/set_up_all_test.dart
@@ -0,0 +1,328 @@
+// Copyright (c) 2015, 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 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void main() {
+ test('runs once before all tests', () {
+ return expectTestsPass(() {
+ var setUpAllRun = false;
+ setUpAll(() {
+ expect(setUpAllRun, isFalse);
+ setUpAllRun = true;
+ });
+
+ test('test 1', () {
+ expect(setUpAllRun, isTrue);
+ });
+
+ test('test 2', () {
+ expect(setUpAllRun, isTrue);
+ });
+ });
+ });
+
+ test('runs once per group, outside-in', () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll1Run = true;
+ });
+
+ group('mid', () {
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll2Run = true;
+ });
+
+ group('inner', () {
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isFalse);
+ setUpAll3Run = true;
+ });
+
+ test('test', () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+ });
+ });
+
+ test('runs before setUps', () {
+ return expectTestsPass(() {
+ var setUpAllRun = false;
+ setUp(() {
+ expect(setUpAllRun, isTrue);
+ });
+
+ setUpAll(() {
+ expect(setUpAllRun, isFalse);
+ setUpAllRun = true;
+ });
+
+ setUp(() {
+ expect(setUpAllRun, isTrue);
+ });
+
+ test('test', () {
+ expect(setUpAllRun, isTrue);
+ });
+ });
+ });
+
+ test('multiples run in order', () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll1Run = true;
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll2Run = true;
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isFalse);
+ setUpAll3Run = true;
+ });
+
+ test('test', () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+
+ group('asynchronously', () {
+ test('blocks additional setUpAlls on in-band async', () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() async {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ await pumpEventQueue();
+ setUpAll1Run = true;
+ });
+
+ setUpAll(() async {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ await pumpEventQueue();
+ setUpAll2Run = true;
+ });
+
+ setUpAll(() async {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isFalse);
+ await pumpEventQueue();
+ setUpAll3Run = true;
+ });
+
+ test('test', () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+
+ test("doesn't block additional setUpAlls on out-of-band async", () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+
+ expect(
+ pumpEventQueue().then((_) {
+ setUpAll1Run = true;
+ }),
+ completes);
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+
+ expect(
+ pumpEventQueue().then((_) {
+ setUpAll2Run = true;
+ }),
+ completes);
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+
+ expect(
+ pumpEventQueue().then((_) {
+ setUpAll3Run = true;
+ }),
+ completes);
+ });
+
+ test('test', () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+ });
+
+ test("isn't run for a skipped group", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync0(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ group('skipped', () {
+ setUpAll(shouldNotRun);
+
+ test('test', () {});
+ }, skip: true);
+ });
+
+ await engine.run();
+ expect(engine.liveTests, hasLength(1));
+ expect(engine.skipped, hasLength(1));
+ expect(engine.liveTests, equals(engine.skipped));
+ });
+
+ test('is emitted through Engine.onTestStarted', () async {
+ var engine = declareEngine(() {
+ setUpAll(() {});
+
+ test('test', () {});
+ });
+
+ var queue = StreamQueue(engine.onTestStarted);
+ var setUpAllFuture = queue.next;
+ var liveTestFuture = queue.next;
+
+ await engine.run();
+
+ var setUpAllLiveTest = await setUpAllFuture;
+ expect(setUpAllLiveTest.test.name, equals('(setUpAll)'));
+ expectTestPassed(setUpAllLiveTest);
+
+ // The fake test for setUpAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, isNot(contains(setUpAllLiveTest)));
+ expect(engine.passed, isNot(contains(setUpAllLiveTest)));
+ expect(engine.failed, isNot(contains(setUpAllLiveTest)));
+ expect(engine.skipped, isNot(contains(setUpAllLiveTest)));
+ expect(engine.active, isNot(contains(setUpAllLiveTest)));
+
+ var liveTest = await liveTestFuture;
+ expectTestPassed(await liveTestFuture);
+ expect(engine.liveTests, contains(liveTest));
+ expect(engine.passed, contains(liveTest));
+ });
+
+ group('with an error', () {
+ test('reports the error and remains in Engine.liveTests', () async {
+ var engine = declareEngine(() {
+ setUpAll(() => throw TestFailure('fail'));
+
+ test('test', () {});
+ });
+
+ var queue = StreamQueue(engine.onTestStarted);
+ var setUpAllFuture = queue.next;
+
+ expect(await engine.run(), isFalse);
+
+ var setUpAllLiveTest = await setUpAllFuture;
+ expect(setUpAllLiveTest.test.name, equals('(setUpAll)'));
+ expectTestFailed(setUpAllLiveTest, 'fail');
+
+ // The fake test for setUpAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, contains(setUpAllLiveTest));
+ expect(engine.failed, contains(setUpAllLiveTest));
+ expect(engine.passed, isNot(contains(setUpAllLiveTest)));
+ expect(engine.skipped, isNot(contains(setUpAllLiveTest)));
+ expect(engine.active, isNot(contains(setUpAllLiveTest)));
+ });
+
+ test("doesn't run tests in the group", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync0(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ setUpAll(() => throw 'error');
+
+ test('test', shouldNotRun);
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test("doesn't run inner groups", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync0(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ setUpAll(() => throw 'error');
+
+ group('group', () {
+ test('test', shouldNotRun);
+ });
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test("doesn't run further setUpAlls", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync0(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ setUpAll(() => throw 'error');
+ setUpAll(shouldNotRun);
+
+ test('test', shouldNotRun);
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+ });
+}
diff --git a/pkgs/test_api/test/frontend/tear_down_all_test.dart b/pkgs/test_api/test/frontend/tear_down_all_test.dart
new file mode 100644
index 0000000..d6d209f
--- /dev/null
+++ b/pkgs/test_api/test/frontend/tear_down_all_test.dart
@@ -0,0 +1,372 @@
+// Copyright (c) 2015, 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 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void main() {
+ test('runs once after all tests', () {
+ return expectTestsPass(() {
+ var test1Run = false;
+ var test2Run = false;
+ var tearDownAllRun = false;
+ tearDownAll(() {
+ expect(test1Run, isTrue);
+ expect(test2Run, isTrue);
+ expect(tearDownAllRun, isFalse);
+ tearDownAllRun = true;
+ });
+
+ test('test 1', () {
+ expect(tearDownAllRun, isFalse);
+ test1Run = true;
+ });
+
+ test('test 2', () {
+ expect(tearDownAllRun, isFalse);
+ test2Run = true;
+ });
+ });
+ });
+
+ test('runs once per group, inside-out', () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ var testRun = false;
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isTrue);
+ expect(tearDownAll3Run, isTrue);
+ expect(testRun, isTrue);
+ tearDownAll1Run = true;
+ });
+
+ group('mid', () {
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isTrue);
+ expect(testRun, isTrue);
+ tearDownAll2Run = true;
+ });
+
+ group('inner', () {
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ expect(testRun, isTrue);
+ tearDownAll3Run = true;
+ });
+
+ test('test', () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ testRun = true;
+ });
+ });
+ });
+ });
+ });
+
+ test('runs after tearDowns', () {
+ return expectTestsPass(() {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDownAllRun = false;
+ tearDown(() {
+ expect(tearDownAllRun, isFalse);
+ tearDown1Run = true;
+ });
+
+ tearDownAll(() {
+ expect(tearDown1Run, isTrue);
+ expect(tearDown2Run, isTrue);
+ expect(tearDownAllRun, isFalse);
+ tearDownAllRun = true;
+ });
+
+ tearDown(() {
+ expect(tearDownAllRun, isFalse);
+ tearDown2Run = true;
+ });
+
+ test('test', () {
+ expect(tearDownAllRun, isFalse);
+ });
+ });
+ });
+
+ test('multiples run in reverse order', () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isTrue);
+ expect(tearDownAll3Run, isTrue);
+ tearDownAll1Run = true;
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isTrue);
+ tearDownAll2Run = true;
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ tearDownAll3Run = true;
+ });
+
+ test('test', () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ });
+ });
+ });
+
+ group('asynchronously', () {
+ test('blocks additional tearDownAlls on in-band async', () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ tearDownAll(() async {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isTrue);
+ expect(tearDownAll3Run, isTrue);
+ await pumpEventQueue();
+ tearDownAll1Run = true;
+ });
+
+ tearDownAll(() async {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isTrue);
+ await pumpEventQueue();
+ tearDownAll2Run = true;
+ });
+
+ tearDownAll(() async {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ await pumpEventQueue();
+ tearDownAll3Run = true;
+ });
+
+ test('test', () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ });
+ });
+ });
+
+ test("doesn't block additional tearDownAlls on out-of-band async", () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+
+ expect(Future(() {
+ tearDownAll1Run = true;
+ }), completes);
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+
+ expect(Future(() {
+ tearDownAll2Run = true;
+ }), completes);
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+
+ expect(Future(() {
+ tearDownAll3Run = true;
+ }), completes);
+ });
+
+ test('test', () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ });
+ });
+ });
+
+ test('blocks further tests on in-band async', () {
+ return expectTestsPass(() {
+ var tearDownAllRun = false;
+ group('group', () {
+ tearDownAll(() async {
+ expect(tearDownAllRun, isFalse);
+ await pumpEventQueue();
+ tearDownAllRun = true;
+ });
+
+ test('test', () {});
+ });
+
+ test('after', () {
+ expect(tearDownAllRun, isTrue);
+ });
+ });
+ });
+
+ test('blocks further tests on out-of-band async', () {
+ return expectTestsPass(() {
+ var tearDownAllRun = false;
+ group('group', () {
+ tearDownAll(() async {
+ expect(tearDownAllRun, isFalse);
+ expect(
+ pumpEventQueue().then((_) {
+ tearDownAllRun = true;
+ }),
+ completes);
+ });
+
+ test('test', () {});
+ });
+
+ test('after', () {
+ expect(tearDownAllRun, isTrue);
+ });
+ });
+ });
+ });
+
+ test("isn't run for a skipped group", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync0(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ group('skipped', () {
+ tearDownAll(shouldNotRun);
+
+ test('test', () {});
+ }, skip: true);
+ });
+
+ await engine.run();
+ expect(engine.liveTests, hasLength(1));
+ expect(engine.skipped, hasLength(1));
+ expect(engine.liveTests, equals(engine.skipped));
+ });
+
+ test('is emitted through Engine.onTestStarted', () async {
+ var engine = declareEngine(() {
+ tearDownAll(() {});
+
+ test('test', () {});
+ });
+
+ var queue = StreamQueue(engine.onTestStarted);
+ var liveTestFuture = queue.next;
+ var tearDownAllFuture = queue.next;
+
+ await engine.run();
+
+ var tearDownAllLiveTest = await tearDownAllFuture;
+ expect(tearDownAllLiveTest.test.name, equals('(tearDownAll)'));
+ expectTestPassed(tearDownAllLiveTest);
+
+ // The fake test for tearDownAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.passed, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.failed, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.skipped, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.active, isNot(contains(tearDownAllLiveTest)));
+
+ var liveTest = await liveTestFuture;
+ expectTestPassed(await liveTestFuture);
+ expect(engine.liveTests, contains(liveTest));
+ expect(engine.passed, contains(liveTest));
+ });
+
+ group('with an error', () {
+ test('reports the error and remains in Engine.liveTests', () async {
+ var engine = declareEngine(() {
+ tearDownAll(() => throw TestFailure('fail'));
+
+ test('test', () {});
+ });
+
+ var queue = StreamQueue(engine.onTestStarted);
+ expect(queue.next, completes);
+ var tearDownAllFuture = queue.next;
+
+ expect(await engine.run(), isFalse);
+
+ var tearDownAllLiveTest = await tearDownAllFuture;
+ expect(tearDownAllLiveTest.test.name, equals('(tearDownAll)'));
+ expectTestFailed(tearDownAllLiveTest, 'fail');
+
+ // The fake test for tearDownAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, contains(tearDownAllLiveTest));
+ expect(engine.failed, contains(tearDownAllLiveTest));
+ expect(engine.passed, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.skipped, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.active, isNot(contains(tearDownAllLiveTest)));
+ });
+
+ test('runs further tearDownAlls', () async {
+ // Declare this in the outer test so if it doesn't runs, the outer test
+ // will fail.
+ var shouldRun = expectAsync0(() {});
+
+ var engine = declareEngine(() {
+ tearDownAll(() => throw 'error');
+ tearDownAll(shouldRun);
+
+ test('test', () {});
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test('runs outer tearDownAlls', () async {
+ // Declare this in the outer test so if it doesn't runs, the outer test
+ // will fail.
+ var shouldRun = expectAsync0(() {});
+
+ var engine = declareEngine(() {
+ tearDownAll(shouldRun);
+
+ group('group', () {
+ tearDownAll(() => throw 'error');
+
+ test('test', () {});
+ });
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+ });
+}
diff --git a/pkgs/test_api/test/frontend/timeout_test.dart b/pkgs/test_api/test/frontend/timeout_test.dart
new file mode 100644
index 0000000..fdfd611
--- /dev/null
+++ b/pkgs/test_api/test/frontend/timeout_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2016, 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 'package:test/test.dart';
+
+void main() {
+ group('Timeout.parse', () {
+ group('for "none"', () {
+ test('successfully parses', () {
+ expect(Timeout.parse('none'), equals(Timeout.none));
+ });
+
+ test('rejects invalid input', () {
+ expect(() => Timeout.parse(' none'), throwsFormatException);
+ expect(() => Timeout.parse('none '), throwsFormatException);
+ expect(() => Timeout.parse('xnone'), throwsFormatException);
+ expect(() => Timeout.parse('nonex'), throwsFormatException);
+ expect(() => Timeout.parse('noxe'), throwsFormatException);
+ });
+ });
+
+ group('for a relative timeout', () {
+ test('successfully parses', () {
+ expect(Timeout.parse('1x'), equals(const Timeout.factor(1)));
+ expect(Timeout.parse('2.5x'), equals(const Timeout.factor(2.5)));
+ expect(Timeout.parse('1.2e3x'), equals(const Timeout.factor(1.2e3)));
+ });
+
+ test('rejects invalid input', () {
+ expect(() => Timeout.parse('.x'), throwsFormatException);
+ expect(() => Timeout.parse('x'), throwsFormatException);
+ expect(() => Timeout.parse('ax'), throwsFormatException);
+ expect(() => Timeout.parse('1x '), throwsFormatException);
+ expect(() => Timeout.parse('1x5m'), throwsFormatException);
+ });
+ });
+
+ group('for an absolute timeout', () {
+ test('successfully parses all supported units', () {
+ expect(Timeout.parse('2d'), equals(const Timeout(Duration(days: 2))));
+ expect(Timeout.parse('2h'), equals(const Timeout(Duration(hours: 2))));
+ expect(
+ Timeout.parse('2m'), equals(const Timeout(Duration(minutes: 2))));
+ expect(
+ Timeout.parse('2s'), equals(const Timeout(Duration(seconds: 2))));
+ expect(Timeout.parse('2ms'),
+ equals(const Timeout(Duration(milliseconds: 2))));
+ expect(Timeout.parse('2us'),
+ equals(const Timeout(Duration(microseconds: 2))));
+ });
+
+ test('supports non-integer units', () {
+ expect(Timeout.parse('2.73d'),
+ equals(Timeout(const Duration(days: 1) * 2.73)));
+ });
+
+ test('supports multiple units', () {
+ expect(
+ Timeout.parse('1d 2h3m 4s5ms\t6us'),
+ equals(const Timeout(Duration(
+ days: 1,
+ hours: 2,
+ minutes: 3,
+ seconds: 4,
+ milliseconds: 5,
+ microseconds: 6))));
+ });
+
+ test('rejects invalid input', () {
+ expect(() => Timeout.parse('.d'), throwsFormatException);
+ expect(() => Timeout.parse('d'), throwsFormatException);
+ expect(() => Timeout.parse('ad'), throwsFormatException);
+ expect(() => Timeout.parse('1z'), throwsFormatException);
+ expect(() => Timeout.parse('1u'), throwsFormatException);
+ expect(() => Timeout.parse('1d5x'), throwsFormatException);
+ expect(() => Timeout.parse('1d*5m'), throwsFormatException);
+ });
+ });
+ });
+}
diff --git a/pkgs/test_api/test/import_restrictions_test.dart b/pkgs/test_api/test/import_restrictions_test.dart
new file mode 100644
index 0000000..3fd9ffd
--- /dev/null
+++ b/pkgs/test_api/test/import_restrictions_test.dart
@@ -0,0 +1,126 @@
+// Copyright (c) 2021, 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:io';
+import 'dart:isolate';
+
+import 'package:analyzer/dart/analysis/analysis_context.dart';
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:glob/glob.dart';
+import 'package:glob/list_local_fs.dart';
+import 'package:graphs/graphs.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+ late _ImportCheck importCheck;
+ setUpAll(() async {
+ importCheck = await _ImportCheck.create();
+ });
+ group('backend', () {
+ test('must not import from other subdirectories', () async {
+ final entryPoints = [
+ _testApiLibrary('backend.dart'),
+ ...await _ImportCheck.findEntrypointsUnder(
+ _testApiLibrary('src/backend'))
+ ];
+ await for (final source
+ in importCheck.transitiveSamePackageSources(entryPoints)) {
+ for (final import in source.imports) {
+ expect(import.pathSegments.skip(1).take(2), ['src', 'backend'],
+ reason: 'Invalid import from ${source.uri} : $import');
+ }
+ }
+ });
+ });
+}
+
+Uri _testApiLibrary(String path) => Uri.parse('package:test_api/$path');
+
+class _ImportCheck {
+ final AnalysisContext _context;
+
+ static Future<Iterable<Uri>> findEntrypointsUnder(Uri uri) async {
+ if (!uri.path.endsWith('/')) {
+ uri = uri.replace(path: '${uri.path}/');
+ }
+ final directory = p.fromUri(await Isolate.resolvePackageUri(uri));
+ return Glob('./**')
+ .listSync(root: directory)
+ .whereType<File>()
+ .map((f) => uri.resolve(p.url.relative(f.path, from: directory)));
+ }
+
+ static Future<_ImportCheck> create() async {
+ final context = await _createAnalysisContext();
+ return _ImportCheck._(context);
+ }
+
+ static Future<AnalysisContext> _createAnalysisContext() async {
+ final libUri = Uri.parse('package:graphs/');
+ final libPath = await _pathForUri(libUri);
+ final packagePath = p.dirname(libPath);
+
+ final contexts =
+ AnalysisContextCollection(includedPaths: [packagePath]).contexts;
+ if (contexts.length != 1) {
+ throw StateError('Expected to find exactly one context, got $contexts');
+ }
+ return contexts.first;
+ }
+
+ static Future<String> _pathForUri(Uri uri) async {
+ final fileUri = await Isolate.resolvePackageUri(uri);
+ if (fileUri == null || !fileUri.isScheme('file')) {
+ throw StateError('Expected to resolve $uri to a file URI, got $fileUri');
+ }
+ return p.fromUri(fileUri);
+ }
+
+ _ImportCheck._(this._context);
+
+ Stream<_Source> transitiveSamePackageSources(Iterable<Uri> entryPoints) {
+ assert(entryPoints.every((e) => e.scheme == 'package'));
+ final package = entryPoints.first.pathSegments.first;
+ assert(entryPoints.skip(1).every((e) => e.pathSegments.first == package));
+ return crawlAsync<Uri, _Source>(
+ entryPoints,
+ (uri) async => _Source(uri, await _findImports(uri, package)),
+ (_, source) => source.imports);
+ }
+
+ Future<Set<Uri>> _findImports(Uri uri, String restrictToPackage) async {
+ var path = await _pathForUri(uri);
+ var analysisSession = _context.currentSession;
+ var parseResult = analysisSession.getParsedUnit(path) as ParsedUnitResult;
+ assert(parseResult.content.isNotEmpty,
+ 'Tried to read an invalid library $uri');
+ return parseResult.unit.directives
+ .whereType<UriBasedDirective>()
+ .map((d) => d.uri.stringValue!)
+ .where((uri) => !uri.startsWith('dart:'))
+ .map((import) => _resolveImport(import, uri))
+ .where((import) => import.pathSegments.first == restrictToPackage)
+ .toSet();
+ }
+
+ static Uri _resolveImport(String import, Uri from) {
+ if (import.startsWith('package:')) return Uri.parse(import);
+ assert(from.scheme == 'package');
+ final package = from.pathSegments.first;
+ final fromPath = p.joinAll(from.pathSegments.skip(1));
+ final path = p.normalize(p.join(p.dirname(fromPath), import));
+ return Uri.parse('package:${p.join(package, path)}');
+ }
+}
+
+class _Source {
+ final Uri uri;
+ final Set<Uri> imports;
+
+ _Source(this.uri, this.imports);
+}
diff --git a/pkgs/test_api/test/utils.dart b/pkgs/test_api/test/utils.dart
new file mode 100644
index 0000000..97058e3
--- /dev/null
+++ b/pkgs/test_api/test/utils.dart
@@ -0,0 +1,146 @@
+// Copyright (c) 2015, 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:collection';
+
+import 'package:test/test.dart';
+import 'package:test_api/src/backend/declarer.dart';
+import 'package:test_api/src/backend/group_entry.dart';
+import 'package:test_api/src/backend/live_test.dart';
+import 'package:test_api/src/backend/runtime.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_api/src/backend/suite_platform.dart';
+import 'package:test_core/src/runner/engine.dart';
+import 'package:test_core/src/runner/plugin/environment.dart';
+import 'package:test_core/src/runner/runner_suite.dart';
+import 'package:test_core/src/runner/suite.dart';
+
+/// A dummy suite platform to use for testing suites.
+final suitePlatform =
+ SuitePlatform(Runtime.vm, compiler: Runtime.vm.defaultCompiler);
+
+// The last state change detected via [expectStates].
+State? lastState;
+
+/// Asserts that exactly [states] will be emitted via [liveTest.onStateChange].
+///
+/// The most recent emitted state is stored in [_lastState].
+void expectStates(LiveTest liveTest, Iterable<State> statesIter) {
+ var states = Queue.of(statesIter);
+ liveTest.onStateChange.listen(expectAsync1((state) {
+ lastState = state;
+ expect(state, equals(states.removeFirst()));
+ }, count: states.length, max: states.length));
+}
+
+/// Asserts that errors will be emitted via [liveTest.onError] that match
+/// [validators], in order.
+void expectErrors(
+ LiveTest liveTest, Iterable<void Function(Object)> validatorsIter) {
+ var validators = Queue.of(validatorsIter);
+ liveTest.onError.listen(expectAsync1((error) {
+ validators.removeFirst()(error.error);
+ }, count: validators.length, max: validators.length));
+}
+
+/// Asserts that [liveTest] will have a single failure with message `"oh no"`.
+void expectSingleFailure(LiveTest liveTest) {
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.failure)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(lastState?.status, equals(Status.complete));
+ expect(error, isTestFailure('oh no'));
+ }
+ ]);
+}
+
+/// Asserts that [liveTest] will have a single error, the string `"oh no"`.
+void expectSingleError(LiveTest liveTest) {
+ expectStates(liveTest, [
+ const State(Status.running, Result.success),
+ const State(Status.complete, Result.error)
+ ]);
+
+ expectErrors(liveTest, [
+ (error) {
+ expect(lastState?.status, equals(Status.complete));
+ expect(error, equals('oh no'));
+ }
+ ]);
+}
+
+/// Returns a matcher that matches a [TestFailure] with the given [message].
+///
+/// [message] can be a string or a [Matcher].
+Matcher isTestFailure(Object message) => const TypeMatcher<TestFailure>()
+ .having((e) => e.message, 'message', message);
+
+/// Asserts that [liveTest] has completed and passed.
+///
+/// If the test had any errors, they're surfaced nicely into the outer test.
+void expectTestPassed(LiveTest liveTest) {
+ // Since the test is expected to pass, we forward any current or future errors
+ // to the outer test, because they're definitely unexpected.
+ for (var error in liveTest.errors) {
+ registerException(error.error, error.stackTrace);
+ }
+ liveTest.onError.listen((error) {
+ registerException(error.error, error.stackTrace);
+ });
+
+ expect(liveTest.state.status, equals(Status.complete));
+ expect(liveTest.state.result, equals(Result.success));
+}
+
+/// Asserts that [liveTest] failed with a single [TestFailure] whose message
+/// matches [message].
+void expectTestFailed(LiveTest liveTest, Object message) {
+ expect(liveTest.state.status, equals(Status.complete));
+ expect(liveTest.state.result, equals(Result.failure));
+ expect(liveTest.errors, hasLength(1));
+ expect(liveTest.errors.first.error, isTestFailure(message));
+}
+
+/// Runs [body] with a declarer, runs all the declared tests, and asserts that
+/// they pass.
+///
+/// This is typically used to run multiple tests where later tests make
+/// assertions about the results of previous ones.
+Future expectTestsPass(void Function() body) async {
+ var engine = declareEngine(body);
+ var success = await engine.run();
+
+ for (var test in engine.liveTests) {
+ expectTestPassed(test);
+ }
+
+ expect(success, isTrue);
+}
+
+/// Runs [body] with a declarer and returns the declared entries.
+List<GroupEntry> declare(
+ void Function() body, {
+ // TODO: Change the default https://github.com/dart-lang/test/issues/1571
+ bool allowDuplicateTestNames = true,
+}) {
+ var declarer = Declarer(allowDuplicateTestNames: allowDuplicateTestNames)
+ ..declare(body);
+ return declarer.build().entries;
+}
+
+/// Runs [body] with a declarer and returns an engine that runs those tests.
+Engine declareEngine(void Function() body, {bool runSkipped = false}) {
+ var declarer = Declarer()..declare(body);
+ return Engine.withSuites([
+ RunnerSuite(
+ const PluginEnvironment(),
+ SuiteConfiguration.runSkipped(runSkipped),
+ declarer.build(),
+ suitePlatform)
+ ]);
+}
diff --git a/pkgs/test_core/CHANGELOG.md b/pkgs/test_core/CHANGELOG.md
new file mode 100644
index 0000000..4c305bc
--- /dev/null
+++ b/pkgs/test_core/CHANGELOG.md
@@ -0,0 +1,668 @@
+## 0.6.8
+
+* Fix hang when running multiple precompiled browser tests.
+
+## 0.6.7
+
+* Update the `package:vm_service` constraint to allow version `15.x`.
+
+## 0.6.6
+
+* Allow `analyzer: '>=6.0.0 <8.0.0'`
+* Fix dart2wasm tests on windows.
+* Increase SDK constraint to ^3.5.0.
+* Allow passing additional arguments to `dart compile wasm`.
+
+## 0.6.5
+
+* Increase SDK constraint to ^3.4.0.
+* Ensure we don't create files ending in a `.`, this breaks windows.
+
+## 0.6.4
+
+* Enable asserts for `dart2wasm` tests.
+
+## 0.6.3
+
+* Update min SDK constraint to 3.2.0.
+* Fix testing with `dart2wasm` - use `dart compile wasm` instead of depending on
+ SDK internals
+
+## 0.6.2
+
+* Add `@doNotSubmit` to more declarations of the `solo` parameter.
+
+## 0.6.1
+
+* Handle missing package configs.
+* Document the silent reporter in CLI help output.
+* Support enabling experiments with the dart2wasm compiler.
+
+## 0.6.0
+
+* Handle paths with leading `/` when spawning test isolates.
+* Added `dart2wasm` as a supported compiler for the `chrome` runtime.
+* **BREAKING**: Removed the `experimentalChromeWasm` runtime.
+* **BREAKING**: Removed `Runtime.isJS` and `Runtime.isWasm`, as this is now
+ based on the compiler and not the runtime.
+* **BREAKING**: Removed `Configuration.pubServeUrl` and support for it.
+* Fix running of tests defined under `lib/` with relative imports to other
+ libraries in the package.
+* Update the `package:frontend_server_client` constraint to allow version
+ `4.0.0`.
+* Update the `package:vm_service` constraint to allow version `14.x`.
+
+## 0.5.9
+
+* Update the vm_service constraint to allow version `13.x`.
+
+## 0.5.8
+
+* Move scaffolding definitions to a non-deprecated library.
+* Allow omitting the `Compiler` argument to `currentPlatform`.
+
+## 0.5.7
+
+* Pass --disable-program-split to dart2js to fix tests which use deferred
+ loading.
+* Add a 'silent' reporter option. Keep it hidden in the CLI args help since it
+ is not useful in the general case, but can be useful for tests of the test
+ runner.
+* Update to `package:vm_service` `12.0.0`
+
+## 0.5.6
+
+* Add support for discontinuing after the first failing test with `--fail-fast`.
+
+## 0.5.5
+
+* Change "compiling <path>" to "loading <path>" message in all cases. Surface
+ the "loading" messages in the situations where previously only "compiling"
+ message would be used.
+
+## 0.5.4
+
+* Drop support for null unsafe Dart, bump SDK constraint to `3.0.0`.
+* Add `final` modifier on some implementation classes: `Configuration`,
+ `CustomRuntime`,`RuntimeSettings`, `SuiteConfiguration`.
+* Fix the `root_` fields in the JSON reporter when running a test on Windows
+ with an absolute path.
+* Allow the latest analyzer (6.x.x).
+
+## 0.5.3
+
+* Fix compatibility with wasm number semantics.
+
+## 0.5.2
+
+* Use the version `0.5.2` of `packge:test_api`.
+
+## 0.5.1
+
+* Start adding experimental support for native_assets.yaml, when
+ `--enable-experiment=native_assets` is passed.
+
+## 0.5.0
+
+* Support the `--compiler` flag, which can be used to configure which compiler
+ to use.
+ * To specify a compiler by platform, the argument supports platform selectors
+ through this syntax `[<platform>:]<compiler>`. For example the command line
+ argument `--compiler vm:source` would run all vm tests from source instead
+ of compiling to kernel first.
+ * If no given compiler is compatible for a platform, it will use its default
+ compiler instead.
+* Add support for `-c exe` (the native executable compiler) to the vm platform.
+* Add `Compiler` class, exposed through `backend.dart`.
+* Support compiler identifiers in platform selectors.
+* List the supported compilers for each platform in the usage text.
+* Update all reporters to print the compiler along with the platform name
+ when configured to print the platform. Extend the logic for printing platofrm
+ information to do so if any compilers are explicitly configured.
+* Deprecate `--use-data-isolate-strategy`. It is now an alias for `-c vm:source`
+ which is roughly equivalent. If this is breaking for you please file an issue.
+* **BREAKING** Add required `defaultCompiler` and `supportedCompilers` fields
+ to `Runtime`.
+* **BREAKING** Add required `compiler` field to `SuitePlatform`.
+* **BREAKING** Add required `compilerSelections` argument to some
+ `Configuration` and `SuiteConfiguration` constructors.
+* **BREAKING** Custom platform plugins need to respect the compiler option
+ given through the `SuitePlatform` argument to `PlatformPlugin.load`. This is
+ not statically breaking but it will be confusing for users if it isn't
+ supported.
+* **BREAKING** Remove `useDataIsolateStrategy` field from `Configuration`.
+* **BREAKING** Stop exporting APIs from `package:matcher/expect.dart`.
+
+## 0.4.24
+
+* Fix running paths by absolute path (with drive letter) on windows.
+
+## 0.4.23
+
+* Avoid empty expandable groups for tests without extra output in Github
+ reporter.
+* Support running tests by absolute file uri.
+* Update `vm_service` constraint to `>=6.0.0 <12.0.0`.
+
+## 0.4.22
+
+* Don't run `tearDown` until the test body and outstanding work is complete,
+ even if the test has already failed.
+* Update `vm_service` constraint to `>=6.0.0 <11.0.0`.
+
+## 0.4.21
+
+* Move `includeTags` and `excludeTags` from `SuiteConfiguration` to
+ `Configuration`.
+* Merge command lines args repeating the same test path to run the suite one
+ time with all the test cases across the different arguments.
+* Fix VM tests which run after some test has changed the working directory.
+ There are still issues with browser tests after changing directory.
+
+## 0.4.20
+
+* Fix an issue with the github reporter where tests that fail asynchronously
+ after they've completed would show up as succeeded tests.
+* Support the latest `package:test_api`.
+* Refactor `CompilerPool` to be abstract, add wasm compiler pool.
+
+## 0.4.19
+
+* Support `package:matcher` version `0.12.13`.
+* Require Dart SDK version 2.18.
+
+## 0.4.18
+
+* Support the latest `package:test_api`.
+* Support the latest `package:analyzer`.
+
+## 0.4.17
+
+* Support the latest `package:test_api`.
+* Support the latest `package:frontend_server_client`.
+
+## 0.4.16
+
+* Make the labels for test loading more readable in the compact and expanded
+ reporters, use gray instead of black.
+* Print a command to re-run the failed test after each failure in the compact
+ reporter.
+* Fix the package config path used when running pre-compiled vm tests.
+
+## 0.4.15
+
+* Support the latest `package:test_api`.
+
+## 0.4.14
+
+* Update the github reporter to output the platform in the test names when
+ multiple platforms are used.
+* Fix `spawnHybridUri` support for `package:` uris.
+
+## 0.4.13
+
+* Re-publish changes from 0.4.12.
+* Stop relying on setUpAllName and tearDownAllName constants from test_api.
+
+## 0.4.12 (retracted)
+
+* Remove wait for VM platform isolate exits.
+* Drop `dart2jsPath` configuration support.
+* Allow loading tests under a path with the directory named `packages`.
+* Require analyzer version `3.3.0`, and allow version `4.x`.
+
+## 0.4.11
+
+* Update `vm_service` constraint to `>=6.0.0 <9.0.0`.
+
+## 0.4.10
+
+* Update `analyzer` constraint to `>=2.14.0 <3.0.0`.
+* Add an `--ignore-timeouts` command line flag, which disables all timeouts
+ for all tests.
+* Experimental: Add a VM service extension `ext.test.pauseAfterTests` which
+ configures VM platform tests to pause for debugging after tests are run,
+ before the test isolates are killed.
+
+## 0.4.9
+
+* Wait for paused VM platform isolates before shutdown.
+
+## 0.4.8
+
+* Add logging about enabling stack trace chaining to the compact and expanded
+ reporters (moved from the invoker). This will now only be logged once after
+ all tests have ran.
+
+## 0.4.7
+
+* Fix parsing of file paths into a URI on windows.
+
+## 0.4.6
+
+* Support query parameters `name`, `full-name`, `line`, and `col` on test paths,
+ which will apply the filters to only those test suites.
+ * All specified filters must match for a test to run.
+ * Global filters (ie: `--name`) are also still respected and must match.
+ * The `line` and `col` will match if any frame from the test trace matches
+ (the test trace is the current stack trace where `test` is invoked).
+* Support the latest `test_api`.
+
+## 0.4.5
+
+* Use newer analyzer APIs.
+
+## 0.4.4
+
+* Support the latest `test_api`.
+
+## 0.4.3
+
+* Add an option to disallow duplicate test or group names in `directRunTests`.
+* Add configuration to disallow duplicate test and group names by default. See
+ the [docs][allow_duplicate_test_names] for more information.
+* Remove dependency on pedantic.
+
+[allow_duplicate_test_names]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#allow_duplicate_test_names
+
+## 0.4.2
+
+* Re-use the cached dill file from previous runs on subsequent runs.
+
+## 0.4.1
+
+* Use the latest `package:matcher`.
+
+## 0.4.0
+
+* **BREAKING**: All parameters to the `SuiteConfiguration` and `Configuration`
+ constructors are now required. Some specialized constructors have been added
+ for the common cases where a subset are intended to be provided.
+* **BREAKING**: Remove support for `FORCE_TEST_EXIT`.
+* Report incomplete tests as errors in the JSON reporter when the run is
+ canceled early.
+* Don't log the --test-randomization-ordering-seed if using the json reporter.
+* Add a new exit code, 79, which is used when no tests were ran.
+ * Previously you would have gotten either exit code 1 or 65 (65 if you had
+ provided a test name regex).
+* When no tests were ran but tags were provided, list the tag configuration.
+* Update `analyzer` constraint to `>=1.0.0 <3.0.0`.
+
+## 0.3.29
+
+* Fix a bug where a tag level configuration would cause test suites with that
+ tag to ignore the `--test-randomize-ordering-seed` argument.
+
+## 0.3.28
+
+* Add `time` field to the json reporters `allSuites` event type so that all
+ event types can be unified.
+
+## 0.3.27
+
+* Restore the `Configuration.loadFromString` constructor.
+
+## 0.3.26
+
+* Give a better error when `printOnFailure` is called from outside a test
+ zone.
+
+## 0.3.25
+
+* Support the latest vm_service release (`7.0.0`).
+
+## 0.3.24
+
+* Fix race condition between compilation of vm tests and the running of
+ isolates.
+
+## 0.3.23
+
+* Forward experiment args from the runner executable to the compiler with the
+ new vm test loading strategy.
+
+## 0.3.22
+
+* Fix a windows issue with the new loading strategy.
+
+## 0.3.21
+
+* Fix an issue where you couldn't have tests compiled in both sound and
+ unsound null safety modes.
+
+## 0.3.20
+
+* Add library `scaffolding.dart` to allow importing a subset of the normal
+ surface area.
+* Remove `suiteChannel`. This is now handled by an additional argument to the
+ `beforeLoad` callback in `serializeSuite`.
+* Disable stack trace chaining by default.
+* Change the default way VM tests are launched and ran to greatly speed up
+ loading performance.
+ * You can force the old strategy with `--use-data-isolate-strategy` flag if
+ you run into issues, but please also file a bug.
+* Improve the error message for `hybridMain` functions with an incompatible
+ StreamChannel parameter type.
+* Change the `message` argument to `PlatformPlugin.load` to `Map<String,
+ Object?>`. In an upcoming release this will be required as the type for this
+ argument when passed through to `deserializeSuite`.
+
+## 0.3.19
+
+* ~~Disable stack trace chaining by default.~~
+
+## 0.3.18
+
+* Update `spawnHybridCode` to default to the current packages language version.
+* Update to the latest `test_api`.
+
+## 0.3.17
+
+* Complete the null safety migration.
+
+## 0.3.16
+
+* Allow package:io version 1.0.0.
+
+## 0.3.14
+
+* Handle issue closing `stdin` during shutdown.
+
+## 0.3.13
+
+* Allow the latest analyzer `1.0.0`.
+
+## 0.3.12
+
+* Stable null safety release.
+
+## 0.3.12-nullsafety.17
+
+* Use the `test_api` for stable null safety.
+
+## 0.3.12-nullsafety.16
+
+* Expand upper bound constraints for some null safe migrated packages.
+
+## 0.3.12-nullsafety.15
+
+* Support the latest vm_service release (`6.x.x`).
+
+## 0.3.12-nullsafety.14
+
+* Support the latest coverage release (`0.15.x`).
+
+## 0.3.12-nullsafety.13
+
+* Allow the latest args release (`2.x`).
+
+## 0.3.12-nullsafety.12
+
+* Allow the latest glob release (`2.x`).
+
+## 0.3.12-nullsafety.11
+
+* Fix `spawnHybridUri` on windows.
+* Allow `package:yaml` version `3.x.x`.
+
+## 0.3.12-nullsafety.10
+
+* Allow `package:analyzer` version `0.41.x`.
+
+## 0.3.12-nullsafety.9
+
+* Fix `spawnHybridUri` to respect language versioning of the spawned uri.
+* Pre-emptively fix legacy library import lint violations, and unmigrate some
+ libraries as necessary.
+
+## 0.3.12-nullsafety.8
+
+* Fix a bug where the test runner could crash when printing the elapsed time.
+* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release
+ guidelines.
+
+
+## 0.3.12-nullsafety.7
+
+* Allow prerelease versions of the 2.12 sdk.
+
+## 0.3.12-nullsafety.6
+
+* Add experimental `directRunTests`, `directRunSingle`, and `enumerateTestCases`
+ APIs to enable test runners written around a single executable that can report
+ and run any single test case.
+
+## 0.3.12-nullsafety.5
+
+* Allow `2.10` stable and `2.11.0-dev` SDKs.
+* Add `src/platform.dart` library to consolidate the necessary imports required
+ to write a custom platform.
+* Stop required a `SILENT_OBSERVATORY` environment variable to run with
+ debugging and the JSON reporter.
+
+## 0.3.12-nullsafety.4
+
+* Support latest `package:vm_service`.
+
+## 0.3.12-nullsafety.3
+
+* Clean up `--help` output.
+
+## 0.3.12-nullsafety.2
+
+* Allow version `0.40.x` of `analyzer`.
+
+## 0.3.12-nullsafety.1
+
+* Update source_maps constraint.
+
+## 0.3.12-nullsafety
+
+* Migrate to null safety.
+
+## 0.3.11+4 (Backport)
+
+* Fix `spawnHybridUri` on windows.
+
+## 0.3.11+3 (Backport)
+
+* Support `package:analyzer` version `0.41.x`.
+
+## 0.3.11+2 (Backport)
+
+* Fix `spawnHybridUri` to respect language versioning of the spawned uri.
+
+## 0.3.11+1
+
+* Allow analyzer 0.40.x.
+
+## 0.3.11
+
+* Update to `matcher` version `0.12.9`.
+
+## 0.3.10
+
+* Prepare for `unawaited` from `package:meta`.
+
+## 0.3.9
+
+* Ignore a null `RunnerSuite` rather than throw an error.
+
+## 0.3.8
+
+* Update vm bootstrapping logic to ensure the bootstrap library has the same
+ language version as the test.
+* Populate `languageVersionComment` in the `Metadata` returned from
+ `parseMetadata`.
+
+## 0.3.7
+
+* Support the latest `package:coverage`.
+
+## 0.3.6
+
+* Expose the `Configuration` class and related classes through `backend.dart`.
+
+## 0.3.5
+
+* Add additional information to an exception when we end up with a null
+ `RunnerSuite`.
+
+* Update vm bootstrapping logic to ensure the bootstrap library has the same
+ language version as the test.
+* Populate `languageVersionComment` in the `Metadata` returned from
+ `parseMetadata`.
+
+## 0.3.4
+
+* Fix error messages for incorrect string literals in test annotations.
+
+## 0.3.3
+
+* Support latest `package:vm_service`.
+
+## 0.3.2
+
+* Drop the `package_resolver` dependency.
+
+## 0.3.1
+
+* Support latest `package:vm_service`.
+* Enable asserts in code running through `spawnHybrid` APIs.
+* Exit with a non-zero code if no tests were ran, whether due to skips or having
+ no tests defined.
+
+## 0.3.0
+
+* Bump minimum SDK to `2.4.0` for safer usage of for-loop elements.
+* Deprecate `PhantomJS` and provide warning when used. Support for `PhantomJS`
+ will be removed in version `2.0.0`.
+* Differentiate between test-randomize-ordering-seed not set and 0 being chosen
+ as the random seed.
+* `deserializeSuite` now takes an optional `gatherCoverage` callback.
+* Support retrying of entire test suites when they fail to load.
+* Fix the `compiling` message in precompiled mode so it says `loading` instead,
+ which is more accurate.
+* Change the behavior of the concurrency setting so that loading and running
+ don't have separate pools.
+ * The loading and running of a test are now done with the same resource, and
+ the concurrency setting uniformly affects each. With `-j1` only a single
+ test will ever be loaded at a time.
+ * Previously the loading pool was 2x larger than the actual concurrency
+ setting which could cause flaky tests due to tests being loaded while
+ other tests were running, even with `-j1`.
+* Avoid printing uncaught errors within `spawnHybridUri`.
+
+## 0.2.18
+
+* Allow `test_api` `0.2.13` to work around a bug in the SDK version `2.3.0`.
+
+## 0.2.17
+
+* Add `file_reporters` configuration option and `--file-reporter` CLI option to
+ allow specifying a separate reporter that writes to a file.
+
+## 0.2.16
+
+* Internal cleanup.
+* Add `customHtmlTemplateFile` configuration option to allow sharing an
+ html template between tests
+* Depend on the latest `test_api`.
+
+## 0.2.15
+
+* Add a `StringSink` argument to reporters to prepare for reporting to a file.
+* Add --test-randomize-ordering-seed` argument to randomize test
+execution order based on a provided seed
+* Depend on the latest `test_api`.
+
+## 0.2.14
+
+* Support the latest `package:analyzer`.
+* Update to latest `package:matcher`. Improves output for instances of private
+ classes.
+
+## 0.2.13
+
+* Depend on the latest `package:test_api`.
+
+## 0.2.12
+
+* Conditionally import coverage logic in `engine.dart`. This ensures the engine
+ is platform agnostic.
+
+## 0.2.11
+
+* Implement code coverage gathering for VM tests.
+
+## 0.2.10
+
+* Add a `--debug` argument for running the VM/Chrome in debug mode.
+
+## 0.2.9+2
+
+* Depend on the latest `test_api`.
+
+## 0.2.9+1
+
+* Allow the latest `package:vm_service`.
+
+## 0.2.9
+
+* Mark `package:test_core` as deprecated to prevent accidental use.
+* Depend on the latest `test_api`.
+
+## 0.2.8
+
+* Depend on `vm_service` instead of `vm_service_lib`.
+* Drop dependency on `pub_semver`.
+* Allow `analyzer` version `0.38.x`.
+
+## 0.2.7
+
+* Depend on `vm_service_lib` instead of `vm_service_client`.
+* Depend on latest `package:analyzer`.
+
+## 0.2.6
+
+* Internal cleanup - fix lints.
+* Use the latest `test_api`.
+
+## 0.2.5
+
+* Fix an issue where non-completed tests were considered passing.
+* Updated `compact` and `expanded` reporters to display non-completed tests.
+
+## 0.2.4
+
+* Avoid `dart:isolate` imports on code loaded in tests.
+* Expose the `parseMetadata` function publicly through a new `backend.dart`
+ import, as well as re-exporting `package:test_api/backend.dart`.
+
+## 0.2.3
+
+* Switch import for `IsolateChannel` for forwards compatibility with `2.0.0`.
+
+## 0.2.2
+
+* Allow `analyzer` version `0.36.x`.
+* Update to matcher version `0.12.5`.
+
+## 0.2.1+1
+
+* Allow `analyzer` version `0.35.x`.
+
+## 0.2.1
+
+* Require Dart SDK `>=2.1.0`.
+* Require latest `test_api`.
+
+## 0.2.0
+
+* Remove `remote_listener.dart` and `suite_channel_manager.dart` from runner
+ and depend on them from `test_api`.
+
+## 0.1.0
+
+* Initial release of `test_core`. Provides the basic API for writing and running
+ tests on the VM.
diff --git a/pkgs/test_core/LICENSE b/pkgs/test_core/LICENSE
new file mode 100644
index 0000000..9972f6e
--- /dev/null
+++ b/pkgs/test_core/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2018, the Dart project authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google LLC nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkgs/test_core/README.md b/pkgs/test_core/README.md
new file mode 100644
index 0000000..67f21a8
--- /dev/null
+++ b/pkgs/test_core/README.md
@@ -0,0 +1,11 @@
+[](https://pub.dev/packages/test_core)
+[](https://pub.dev/packages/test_core/publisher)
+
+A minimal package for writing and running tests as well as extensions for
+implementing a custom test runner.
+
+If you're interested in testing Dart code, you likely want to use
+[package:test](https://pub.dev/packages/test).
+
+At this time this package is not intended to be used publicly. The API is not
+yet stable.
diff --git a/pkgs/test_core/lib/backend.dart b/pkgs/test_core/lib/backend.dart
new file mode 100644
index 0000000..f5a3b46
--- /dev/null
+++ b/pkgs/test_core/lib/backend.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2019, 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.
+
+@Deprecated('package:test_core is not intended for general use. '
+ 'Please use package:test.')
+library;
+
+export 'package:test_api/backend.dart'
+ show Compiler, Metadata, PlatformSelector, Runtime, SuitePlatform;
+
+export 'src/runner/configuration.dart' show Configuration;
+export 'src/runner/configuration/custom_runtime.dart' show CustomRuntime;
+export 'src/runner/configuration/runtime_settings.dart' show RuntimeSettings;
+export 'src/runner/parse_metadata.dart' show parseMetadata;
+export 'src/runner/suite.dart' show SuiteConfiguration;
diff --git a/pkgs/test_core/lib/scaffolding.dart b/pkgs/test_core/lib/scaffolding.dart
new file mode 100644
index 0000000..90304d2
--- /dev/null
+++ b/pkgs/test_core/lib/scaffolding.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2021, 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.
+
+@Deprecated('package:test_core is not intended for general use. '
+ 'Please use package:test.')
+library;
+
+export 'src/scaffolding.dart';
diff --git a/pkgs/test_core/lib/src/bootstrap/vm.dart b/pkgs/test_core/lib/src/bootstrap/vm.dart
new file mode 100644
index 0000000..05604f1
--- /dev/null
+++ b/pkgs/test_core/lib/src/bootstrap/vm.dart
@@ -0,0 +1,50 @@
+// 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.
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:stream_channel/isolate_channel.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+import '../runner/plugin/remote_platform_helpers.dart';
+import '../runner/plugin/shared_platform_helpers.dart';
+
+/// Bootstraps a vm test to communicate with the test runner over an isolate.
+void internalBootstrapVmTest(Function Function() getMain, SendPort sendPort) {
+ var platformChannel =
+ MultiChannel<Object?>(IsolateChannel<Object?>.connectSend(sendPort));
+ var testControlChannel = platformChannel.virtualChannel()
+ ..pipe(serializeSuite(getMain));
+ platformChannel.sink.add(testControlChannel.id);
+
+ platformChannel.stream.forEach((message) {
+ assert(message == 'debug');
+ debugger(message: 'Paused by test runner');
+ platformChannel.sink.add('done');
+ });
+}
+
+/// Bootstraps a native executable test to communicate with the test runner over
+/// a socket.
+void internalBootstrapNativeTest(
+ Function Function() getMain, List<String> args) async {
+ if (args.length != 2) {
+ throw StateError(
+ 'Expected exactly two args, a host and a port, but got $args');
+ }
+ var socket = await Socket.connect(args[0], int.parse(args[1]));
+ var platformChannel = MultiChannel<Object?>(jsonSocketStreamChannel(socket));
+ var testControlChannel = platformChannel.virtualChannel()
+ ..pipe(serializeSuite(getMain));
+ platformChannel.sink.add(testControlChannel.id);
+
+ unawaited(platformChannel.stream.forEach((message) {
+ assert(message == 'debug');
+ debugger(message: 'Paused by test runner');
+ platformChannel.sink.add('done');
+ }));
+}
diff --git a/pkgs/test_core/lib/src/direct_run.dart b/pkgs/test_core/lib/src/direct_run.dart
new file mode 100644
index 0000000..c574257
--- /dev/null
+++ b/pkgs/test_core/lib/src/direct_run.dart
@@ -0,0 +1,134 @@
+// Copyright (c) 2020, 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:collection';
+
+import 'package:path/path.dart' as p;
+import 'package:test_api/backend.dart';
+import 'package:test_api/src/backend/declarer.dart'; //ignore: implementation_imports
+import 'package:test_api/src/backend/group.dart'; //ignore: implementation_imports
+import 'package:test_api/src/backend/group_entry.dart'; //ignore: implementation_imports
+import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/test.dart'; //ignore: implementation_imports
+
+import 'runner/configuration.dart';
+import 'runner/engine.dart';
+import 'runner/plugin/environment.dart';
+import 'runner/reporter.dart';
+import 'runner/reporter/expanded.dart';
+import 'runner/runner_suite.dart';
+import 'runner/suite.dart';
+import 'util/os.dart';
+import 'util/print_sink.dart';
+
+/// Runs all unskipped test cases declared in [testMain].
+///
+/// Test suite level metadata defined in annotations is not read. No filtering
+/// is applied except for the filtering defined by `solo` or `skip` arguments to
+/// `group` and `test`. Returns [true] if all tests passed.
+Future<bool> directRunTests(FutureOr<void> Function() testMain,
+ {Reporter Function(Engine)? reporterFactory,
+ // TODO: Change the default https://github.com/dart-lang/test/issues/1571
+ bool allowDuplicateTestNames = true}) =>
+ _directRunTests(testMain,
+ reporterFactory: reporterFactory,
+ allowDuplicateTestNames: allowDuplicateTestNames);
+
+/// Runs a single test declared in [testMain] matched by it's full test name.
+///
+/// There must be exactly one test defined with the name [fullTestName]. Note
+/// that not all tests and groups are checked, so a test case that is not be
+/// intended to be run (due to a `solo` on a different test) may still be run
+/// with this API. Only the test names returned by [enumerateTestCases] should
+/// be used to prevent running skipped tests.
+///
+/// Return [true] if the test passes.
+///
+/// If there are no tests matching [fullTestName] a [MissingTestException] is
+/// thrown. If there is more than one test with the name [fullTestName] they
+/// will both be run, then a [DuplicateTestnameException] will be thrown.
+Future<bool> directRunSingleTest(
+ FutureOr<void> Function() testMain, String fullTestName,
+ {Reporter Function(Engine)? reporterFactory}) =>
+ _directRunTests(testMain,
+ reporterFactory: reporterFactory,
+ fullTestName: fullTestName,
+ allowDuplicateTestNames: false);
+
+Future<bool> _directRunTests(FutureOr<void> Function() testMain,
+ {Reporter Function(Engine)? reporterFactory,
+ String? fullTestName,
+ required bool allowDuplicateTestNames}) async {
+ reporterFactory ??= (engine) => ExpandedReporter.watch(engine, PrintSink(),
+ color: Configuration.empty.color, printPath: false, printPlatform: false);
+ final declarer = Declarer(
+ fullTestName: fullTestName,
+ allowDuplicateTestNames: allowDuplicateTestNames);
+ await declarer.declare(testMain);
+
+ final suite = RunnerSuite(
+ const PluginEnvironment(),
+ SuiteConfiguration.empty,
+ declarer.build(),
+ SuitePlatform(Runtime.vm, compiler: null, os: currentOSGuess),
+ path: p.prettyUri(Uri.base));
+
+ final engine = Engine()
+ ..suiteSink.add(suite)
+ ..suiteSink.close();
+
+ reporterFactory(engine);
+
+ final success = await runZoned(() => Invoker.guard(engine.run),
+ zoneValues: {#test.declarer: declarer}) ??
+ false;
+
+ if (fullTestName != null) {
+ final testCount = engine.liveTests.length;
+ if (testCount == 0) {
+ throw MissingTestException(fullTestName);
+ }
+ }
+ return success;
+}
+
+/// Runs [testMain] and returns the names of all declared tests.
+///
+/// Test names declared must be unique. If any test repeats the full name,
+/// including group prefixes, of a prior test a [DuplicateTestNameException]
+/// will be thrown.
+///
+/// Skipped tests are ignored.
+Future<Set<String>> enumerateTestCases(
+ FutureOr<void> Function() testMain) async {
+ final declarer = Declarer();
+ await declarer.declare(testMain);
+
+ final toVisit = Queue<GroupEntry>.of([declarer.build()]);
+ final unskippedTestNames = <String>{};
+ while (toVisit.isNotEmpty) {
+ final current = toVisit.removeLast();
+ if (current is Group) {
+ toVisit.addAll(current.entries.reversed);
+ } else if (current is Test) {
+ if (current.metadata.skip) continue;
+ unskippedTestNames.add(current.name);
+ } else {
+ throw StateError('Unhandled Group Entry: ${current.runtimeType}');
+ }
+ }
+ return unskippedTestNames;
+}
+
+/// An exception thrown when a specific test was requested by name that does not
+/// exist.
+class MissingTestException implements Exception {
+ final String name;
+ MissingTestException(this.name);
+
+ @override
+ String toString() =>
+ 'A test with the name "$name" was not declared in the test suite.';
+}
diff --git a/pkgs/test_core/lib/src/executable.dart b/pkgs/test_core/lib/src/executable.dart
new file mode 100644
index 0000000..eeefd16
--- /dev/null
+++ b/pkgs/test_core/lib/src/executable.dart
@@ -0,0 +1,196 @@
+// Copyright (c) 2015, 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:io';
+
+import 'package:async/async.dart';
+import 'package:path/path.dart' as p;
+import 'package:source_span/source_span.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test_api/src/backend/util/pretty_print.dart'; // ignore: implementation_imports
+
+import 'runner.dart';
+import 'runner/application_exception.dart';
+import 'runner/configuration.dart';
+import 'runner/no_tests_found_exception.dart';
+import 'runner/version.dart';
+import 'util/errors.dart';
+import 'util/exit_codes.dart' as exit_codes;
+import 'util/io.dart';
+
+StreamSubscription? signalSubscription;
+bool isShutdown = false;
+
+/// Returns the path to the global test configuration file.
+final String _globalConfigPath = () {
+ if (Platform.environment.containsKey('DART_TEST_CONFIG')) {
+ return Platform.environment['DART_TEST_CONFIG']!;
+ } else if (Platform.operatingSystem == 'windows') {
+ return p.join(Platform.environment['LOCALAPPDATA']!, 'DartTest.yaml');
+ } else {
+ return '${Platform.environment['HOME']}/.dart_test.yaml';
+ }
+}();
+
+Future<void> main(List<String> args) async {
+ await _execute(args);
+ completeShutdown();
+}
+
+// ignore: unreachable_from_main
+Future<void> runTests(List<String> args) async {
+ await _execute(args);
+}
+
+void completeShutdown() {
+ if (isShutdown) return;
+ if (signalSubscription != null) {
+ signalSubscription!.cancel();
+ signalSubscription = null;
+ }
+ isShutdown = true;
+ cancelStdinLines();
+}
+
+Future<void> _execute(List<String> args) async {
+ /// A merged stream of all signals that tell the test runner to shut down
+ /// gracefully.
+ ///
+ /// Signals will only be captured as long as this has an active subscription.
+ /// Otherwise, they'll be handled by Dart's default signal handler, which
+ /// terminates the program immediately.
+ final signals = Platform.isWindows
+ ? ProcessSignal.sigint.watch()
+ : Platform.isFuchsia // Signals don't exist on Fuchsia.
+ ? const Stream<Never>.empty()
+ : StreamGroup.merge(
+ [ProcessSignal.sigterm.watch(), ProcessSignal.sigint.watch()]);
+
+ Configuration configuration;
+ try {
+ configuration = Configuration.parse(args);
+ } on FormatException catch (error) {
+ _printUsage(error.message);
+ exitCode = exit_codes.usage;
+ return;
+ }
+
+ if (configuration.help) {
+ _printUsage();
+ return;
+ }
+
+ if (configuration.version) {
+ var version = testVersion;
+ if (version == null) {
+ stderr.writeln("Couldn't find version number.");
+ exitCode = exit_codes.data;
+ } else {
+ print(version);
+ }
+ return;
+ }
+
+ try {
+ var fileConfiguration = Configuration.empty;
+ if (File(_globalConfigPath).existsSync()) {
+ fileConfiguration = fileConfiguration
+ .merge(Configuration.load(_globalConfigPath, global: true));
+ }
+
+ if (File(configuration.configurationPath).existsSync()) {
+ fileConfiguration = fileConfiguration
+ .merge(Configuration.load(configuration.configurationPath));
+ }
+
+ configuration = fileConfiguration.merge(configuration);
+ } on SourceSpanFormatException catch (error) {
+ stderr.writeln(error.toString(color: configuration.color));
+ exitCode = exit_codes.data;
+ return;
+ } on FormatException catch (error) {
+ stderr.writeln(error.message);
+ exitCode = exit_codes.data;
+ return;
+ } on IOException catch (error) {
+ stderr.writeln(error.toString());
+ exitCode = exit_codes.noInput;
+ return;
+ }
+
+ var undefinedPresets = configuration.chosenPresets
+ .where((preset) => !configuration.knownPresets.contains(preset))
+ .toList();
+ if (undefinedPresets.isNotEmpty) {
+ _printUsage("Undefined ${pluralize('preset', undefinedPresets.length)} "
+ "${toSentence(undefinedPresets.map((preset) => '"$preset"'))}.");
+ exitCode = exit_codes.usage;
+ return;
+ }
+
+ if (!configuration.explicitPaths &&
+ !Directory(configuration.testSelections.keys.single).existsSync()) {
+ _printUsage('No test files were passed and the default "test/" '
+ "directory doesn't exist.");
+ exitCode = exit_codes.data;
+ return;
+ }
+
+ Runner? runner;
+
+ signalSubscription ??= signals.listen((signal) async {
+ completeShutdown();
+ await runner?.close();
+ });
+
+ try {
+ runner = Runner(configuration);
+ exitCode = (await runner.run()) ? 0 : 1;
+ } on ApplicationException catch (error) {
+ stderr.writeln(error.message);
+ exitCode = exit_codes.data;
+ } on SourceSpanFormatException catch (error) {
+ stderr.writeln(error.toString(color: configuration.color));
+ exitCode = exit_codes.data;
+ } on FormatException catch (error) {
+ stderr.writeln(error.message);
+ exitCode = exit_codes.data;
+ } on NoTestsFoundException catch (error) {
+ stderr.writeln(error.message);
+ exitCode = exit_codes.noTestsRan;
+ } catch (error, stackTrace) {
+ stderr.writeln(getErrorMessage(error));
+ stderr.writeln(Trace.from(stackTrace).terse);
+ stderr.writeln('This is an unexpected error. Please file an issue at '
+ 'http://github.com/dart-lang/test\n'
+ 'with the stack trace and instructions for reproducing the error.');
+ exitCode = exit_codes.software;
+ } finally {
+ await runner?.close();
+ }
+
+ return;
+}
+
+/// Print usage information for this command.
+///
+/// If [error] is passed, it's used in place of the usage message and the whole
+/// thing is printed to stderr instead of stdout.
+void _printUsage([String? error]) {
+ var output = stdout;
+
+ var message = 'Runs tests in this package.';
+ if (error != null) {
+ message = error;
+ output = stderr;
+ }
+
+ output.write('''${wordWrap(message)}
+
+Usage: dart test [files or directories...]
+
+${Configuration.usage}
+''');
+}
diff --git a/pkgs/test_core/lib/src/platform.dart b/pkgs/test_core/lib/src/platform.dart
new file mode 100644
index 0000000..af457b4
--- /dev/null
+++ b/pkgs/test_core/lib/src/platform.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2020, 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.
+
+export 'package:test_api/backend.dart' show Runtime, SuitePlatform;
+export 'package:test_core/src/runner/configuration.dart' show Configuration;
+export 'package:test_core/src/runner/environment.dart'
+ show Environment, PluginEnvironment;
+export 'package:test_core/src/runner/hack_register_platform.dart'
+ show registerPlatformPlugin;
+export 'package:test_core/src/runner/platform.dart' show PlatformPlugin;
+export 'package:test_core/src/runner/plugin/platform_helpers.dart'
+ show deserializeSuite;
+export 'package:test_core/src/runner/runner_suite.dart'
+ show RunnerSuite, RunnerSuiteController;
+export 'package:test_core/src/runner/suite.dart' show SuiteConfiguration;
diff --git a/pkgs/test_core/lib/src/runner.dart b/pkgs/test_core/lib/src/runner.dart
new file mode 100644
index 0000000..ee648d4
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner.dart
@@ -0,0 +1,475 @@
+// Copyright (c) 2015, 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:io';
+
+import 'package:async/async.dart';
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test_api/backend.dart'
+ show PlatformSelector, Runtime, SuitePlatform;
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/group_entry.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/operating_system.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/util/pretty_print.dart'; // ignore: implementation_imports
+
+import 'runner/configuration.dart';
+import 'runner/configuration/reporters.dart';
+import 'runner/debugger.dart';
+import 'runner/engine.dart';
+import 'runner/load_exception.dart';
+import 'runner/load_suite.dart';
+import 'runner/loader.dart';
+import 'runner/no_tests_found_exception.dart';
+import 'runner/reporter.dart';
+import 'runner/reporter/compact.dart';
+import 'runner/reporter/expanded.dart';
+import 'runner/reporter/multiplex.dart';
+import 'runner/runner_suite.dart';
+import 'util/io.dart';
+
+/// A class that loads and runs tests based on a [Configuration].
+///
+/// This maintains a [Loader] and an [Engine] and passes test suites from one to
+/// the other, as well as printing out tests with a [CompactReporter] or an
+/// [ExpandedReporter].
+class Runner {
+ /// The test runner configuration.
+ final _config = Configuration.current;
+
+ /// The loader that loads the test suites from the filesystem.
+ final _loader = Loader();
+
+ /// The engine that runs the test suites.
+ final Engine _engine;
+
+ /// The reporter that's emitting the test runner's results.
+ final Reporter _reporter;
+
+ /// The subscription to the stream returned by [_loadSuites].
+ StreamSubscription? _suiteSubscription;
+
+ /// The set of suite paths for which [_warnForUnknownTags] has already been
+ /// called.
+ ///
+ /// This is used to avoid printing duplicate warnings when a suite is loaded
+ /// on multiple platforms.
+ final _tagWarningSuites = <String>{};
+
+ /// The current debug operation, if any.
+ ///
+ /// This is stored so that we can cancel it when the runner is closed.
+ CancelableOperation? _debugOperation;
+
+ /// The memoizer for ensuring [close] only runs once.
+ final _closeMemo = AsyncMemoizer<void>();
+ bool get _closed => _closeMemo.hasRun;
+
+ /// Sinks created for each file reporter (if there are any).
+ final List<IOSink> _sinks;
+
+ /// Creates a new runner based on [configuration].
+ factory Runner(Configuration config) => config.asCurrent(() {
+ var engine = Engine(
+ concurrency: config.concurrency,
+ coverage: config.coverage,
+ testRandomizeOrderingSeed: config.testRandomizeOrderingSeed,
+ stopOnFirstFailure: config.stopOnFirstFailure,
+ );
+
+ var sinks = <IOSink>[];
+ Reporter createFileReporter(String reporterName, String filepath) {
+ final sink =
+ (File(filepath)..createSync(recursive: true)).openWrite();
+ sinks.add(sink);
+ return allReporters[reporterName]!.factory(config, engine, sink);
+ }
+
+ return Runner._(
+ engine,
+ MultiplexReporter([
+ // Standard reporter.
+ allReporters[config.reporter]!.factory(config, engine, stdout),
+ // File reporters.
+ for (var reporter in config.fileReporters.keys)
+ createFileReporter(reporter, config.fileReporters[reporter]!),
+ ]),
+ sinks,
+ );
+ });
+
+ Runner._(this._engine, this._reporter, this._sinks);
+
+ /// Starts the runner.
+ ///
+ /// This starts running tests and printing their progress. It returns whether
+ /// or not they ran successfully.
+ Future<bool> run() => _config.asCurrent(() async {
+ if (_closed) {
+ throw StateError('run() may not be called on a closed Runner.');
+ }
+
+ _warnForUnsupportedPlatforms();
+
+ var suites = _loadSuites();
+
+ if (_config.coverage != null) {
+ await Directory(_config.coverage!).create(recursive: true);
+ }
+
+ bool? success;
+ if (_config.pauseAfterLoad) {
+ success = await _loadThenPause(suites);
+ } else {
+ var subscription =
+ _suiteSubscription = suites.listen(_engine.suiteSink.add);
+ var results = await Future.wait(<Future>[
+ subscription
+ .asFuture<void>()
+ .then((_) => _engine.suiteSink.close()),
+ _engine.run()
+ ], eagerError: true);
+ success = results.last as bool?;
+ }
+
+ if (_closed) return false;
+
+ if (_engine.passed.isEmpty &&
+ _engine.failed.isEmpty &&
+ _engine.skipped.isEmpty) {
+ if (_config.globalPatterns.isNotEmpty) {
+ var patterns = toSentence(_config.globalPatterns.map((pattern) =>
+ pattern is RegExp
+ ? 'regular expression "${pattern.pattern}"'
+ : '"$pattern"'));
+ throw NoTestsFoundException('No tests match $patterns.');
+ } else if (_config.includeTags != BooleanSelector.all ||
+ _config.excludeTags != BooleanSelector.none) {
+ throw NoTestsFoundException(
+ 'No tests match the requested tag selectors:\n'
+ ' include: "${_config.includeTags}"\n'
+ ' exclude: "${_config.excludeTags}"');
+ } else {
+ throw NoTestsFoundException('No tests were found.');
+ }
+ }
+
+ return (success ?? false) &&
+ (_engine.passed.isNotEmpty || _engine.skipped.isNotEmpty);
+ });
+
+ /// Emits a warning if the user is trying to run on a platform that's
+ /// unsupported for the entire package.
+ void _warnForUnsupportedPlatforms() {
+ var testOn = _config.suiteDefaults.metadata.testOn;
+ if (testOn == PlatformSelector.all) return;
+
+ var unsupportedRuntimes = _config.suiteDefaults.runtimes
+ .map(_loader.findRuntime)
+ .whereType<Runtime>()
+ .where((runtime) => !testOn.evaluate(currentPlatform(runtime, null)))
+ .toList();
+
+ if (unsupportedRuntimes.isEmpty) return;
+
+ // Human-readable names for all unsupported runtimes.
+ var unsupportedNames = <String>[];
+
+ // If the user tried to run on one or more unsupported browsers, figure out
+ // whether we should warn about the individual browsers or whether all
+ // browsers are unsupported.
+ var unsupportedBrowsers =
+ unsupportedRuntimes.where((platform) => platform.isBrowser);
+ if (unsupportedBrowsers.isNotEmpty) {
+ var supportsAnyBrowser = _loader.allRuntimes
+ .where((runtime) => runtime.isBrowser)
+ .any((runtime) => testOn.evaluate(currentPlatform(runtime, null)));
+
+ if (supportsAnyBrowser) {
+ unsupportedNames
+ .addAll(unsupportedBrowsers.map((runtime) => runtime.name));
+ } else {
+ unsupportedNames.add('browsers');
+ }
+ }
+
+ // If the user tried to run on the VM and it's not supported, figure out if
+ // that's because of the current OS or whether the VM is unsupported.
+ if (unsupportedRuntimes.contains(Runtime.vm)) {
+ var supportsAnyOS = OperatingSystem.all.any((os) => testOn.evaluate(
+ SuitePlatform(Runtime.vm,
+ compiler: null, os: os, inGoogle: inGoogle)));
+
+ if (supportsAnyOS) {
+ unsupportedNames.add(currentOS.name);
+ } else {
+ unsupportedNames.add('the Dart VM');
+ }
+ }
+
+ warn("this package doesn't support running tests on "
+ '${toSentence(unsupportedNames, conjunction: 'or')}.');
+ }
+
+ /// Closes the runner.
+ ///
+ /// This stops any future test suites from running. It will wait for any
+ /// currently-running VM tests, in case they have stuff to clean up on the
+ /// filesystem.
+ Future close() => _closeMemo.runOnce(() async {
+ Timer? timer;
+ if (!_engine.isIdle) {
+ // Wait a bit to print this message, since printing it eagerly looks weird
+ // if the tests then finish immediately.
+ timer = Timer(const Duration(seconds: 1), () {
+ // Pause the reporter while we print to ensure that we don't interfere
+ // with its output.
+ _reporter.pause();
+ print('Waiting for current test(s) to finish.');
+ print('Press Control-C again to terminate immediately.');
+ _reporter.resume();
+ });
+ }
+
+ await _debugOperation?.cancel();
+ await _suiteSubscription?.cancel();
+
+ _suiteSubscription = null;
+
+ // Make sure we close the engine *before* the loader. Otherwise,
+ // LoadSuites provided by the loader may get into bad states.
+ //
+ // We close the loader's browsers while we're closing the engine because
+ // browser tests don't store any state we care about and we want them to
+ // shut down without waiting for their tear-downs.
+ await Future.wait([_loader.closeEphemeral(), _engine.close()]);
+ timer?.cancel();
+ await _loader.close();
+
+ // Flush any IOSinks created for file reporters.
+ await Future.wait(_sinks.map((s) => s.flush().then((_) => s.close())));
+ _sinks.clear();
+ });
+
+ /// Return a stream of [LoadSuite]s in [_config.testSelections].
+ ///
+ /// Only tests that match [_config.patterns] will be included in the
+ /// suites once they're loaded.
+ Stream<LoadSuite> _loadSuites() {
+ return StreamGroup.merge(_config.testSelections.entries.map((pathEntry) {
+ final testPath = pathEntry.key;
+ final testSelections = pathEntry.value;
+ final suiteConfig = _config.suiteDefaults.selectTests(testSelections);
+ if (Directory(testPath).existsSync()) {
+ return _loader.loadDir(testPath, suiteConfig);
+ } else if (File(testPath).existsSync()) {
+ return _loader.loadFile(testPath, suiteConfig);
+ } else {
+ return Stream.fromIterable([
+ LoadSuite.forLoadException(
+ LoadException(testPath, 'Does not exist.'),
+ suiteConfig,
+ ),
+ ]);
+ }
+ })).map((loadSuite) {
+ return loadSuite.changeSuite((suite) {
+ _warnForUnknownTags(suite);
+
+ return _shardSuite(suite.filter((test) {
+ // Skip any tests that don't match all the global patterns.
+ if (!_config.globalPatterns
+ .every((pattern) => test.name.contains(pattern))) {
+ return false;
+ }
+
+ // If the user provided tags, skip tests that don't match all of them.
+ if (!_config.includeTags.evaluate(test.metadata.tags.contains)) {
+ return false;
+ }
+
+ // Skip tests that do match any tags the user wants to exclude.
+ if (_config.excludeTags.evaluate(test.metadata.tags.contains)) {
+ return false;
+ }
+
+ final testSelections = suite.config.testSelections;
+ assert(testSelections.isNotEmpty, 'Tests should have been selected');
+ return testSelections.any((selection) {
+ // Skip tests that don't match all the suite specific patterns.
+ if (!selection.testPatterns
+ .every((pattern) => test.name.contains(pattern))) {
+ return false;
+ }
+ // Skip tests that don't start on `line` or `col` if specified.
+ var line = selection.line;
+ var col = selection.col;
+ if (line == null && col == null) return true;
+ var trace = test.trace;
+ if (trace == null) {
+ throw StateError(
+ 'Cannot filter by line/column for this test suite, no stack'
+ 'trace available.');
+ }
+ var path = suite.path;
+ if (path == null) {
+ throw StateError(
+ 'Cannot filter by line/column for this test suite, no suite'
+ 'path available.');
+ }
+ // The absolute path as it will appear in stack traces.
+ var absoluteSuitePath = File(path).absolute.uri.toFilePath();
+
+ bool matchLineAndCol(Frame frame) {
+ switch (frame.uri.scheme) {
+ case 'file':
+ if (frame.uri.toFilePath() != absoluteSuitePath) {
+ return false;
+ }
+ case 'package':
+ // It is unlikely that the test case is specified in a
+ // package: URI. Ignore this case.
+ return false;
+ default:
+ // Now we can assume that the kernel is compiled using
+ // --filesystem-scheme.
+ // In this case, because we don't know the --filesystem-root, as
+ // long as the file path matches we assume it is the same file.
+ if (!absoluteSuitePath.endsWith(frame.uri.path)) {
+ return false;
+ }
+ }
+ if (line != null && frame.line != line) {
+ return false;
+ }
+ if (col != null && frame.column != col) {
+ return false;
+ }
+ return true;
+ }
+
+ return trace.frames.any(matchLineAndCol);
+ });
+ }));
+ });
+ });
+ }
+
+ /// Prints a warning for any unknown tags referenced in [suite] or its
+ /// children.
+ void _warnForUnknownTags(Suite suite) {
+ if (_tagWarningSuites.contains(suite.path)) return;
+ _tagWarningSuites.add(suite.path!);
+
+ var unknownTags = _collectUnknownTags(suite);
+ if (unknownTags.isEmpty) return;
+
+ var yellow = _config.color ? '\u001b[33m' : '';
+ var bold = _config.color ? '\u001b[1m' : '';
+ var noColor = _config.color ? '\u001b[0m' : '';
+
+ var buffer = StringBuffer()
+ ..write('${yellow}Warning:$noColor ')
+ ..write(unknownTags.length == 1 ? 'A tag was ' : 'Tags were ')
+ ..write('used that ')
+ ..write(unknownTags.length == 1 ? "wasn't " : "weren't ")
+ ..writeln('specified in dart_test.yaml.');
+
+ unknownTags.forEach((tag, entries) {
+ buffer.write(' $bold$tag$noColor was used in');
+
+ if (entries.length == 1) {
+ buffer.writeln(' ${_entryDescription(entries.single)}');
+ return;
+ }
+
+ buffer.write(':');
+ for (var entry in entries) {
+ buffer.write('\n ${_entryDescription(entry)}');
+ }
+ buffer.writeln();
+ });
+
+ print(buffer.toString());
+ }
+
+ /// Collects all tags used by [suite] or its children that aren't also passed
+ /// on the command line.
+ ///
+ /// This returns a map from tag names to lists of entries that use those tags.
+ Map<String, List<GroupEntry>> _collectUnknownTags(Suite suite) {
+ var unknownTags = <String, List<GroupEntry>>{};
+ var currentTags = <String>{};
+
+ void collect(GroupEntry entry) {
+ var newTags = <String>{};
+ for (var unknownTag
+ in entry.metadata.tags.difference(_config.knownTags)) {
+ if (currentTags.contains(unknownTag)) continue;
+ unknownTags.putIfAbsent(unknownTag, () => []).add(entry);
+ newTags.add(unknownTag);
+ }
+
+ if (entry is! Group) return;
+
+ currentTags.addAll(newTags);
+ for (var child in entry.entries) {
+ collect(child);
+ }
+ currentTags.removeAll(newTags);
+ }
+
+ collect(suite.group);
+ return unknownTags;
+ }
+
+ /// Returns a human-readable description of [entry], including its type.
+ String _entryDescription(GroupEntry entry) {
+ if (entry is Test) return 'the test "${entry.name}"';
+ if (entry.name.isNotEmpty) return 'the group "${entry.name}"';
+ return 'the suite itself';
+ }
+
+ /// If sharding is enabled, filters [suite] to only include the tests that
+ /// should be run in this shard.
+ ///
+ /// We just take a slice of the tests in each suite corresponding to the shard
+ /// index. This makes the tests pretty tests across shards, and since the
+ /// tests are continuous, makes us more likely to be able to re-use
+ /// `setUpAll()` logic.
+ RunnerSuite _shardSuite(RunnerSuite suite) {
+ if (_config.totalShards == null) return suite;
+
+ var shardSize = suite.group.testCount / _config.totalShards!;
+ var shardIndex = _config.shardIndex!;
+ var shardStart = (shardSize * shardIndex).round();
+ var shardEnd = (shardSize * (shardIndex + 1)).round();
+
+ var count = -1;
+ var filtered = suite.filter((test) {
+ count++;
+ return count >= shardStart && count < shardEnd;
+ });
+
+ return filtered;
+ }
+
+ /// Loads each suite in [suites] in order, pausing after load for runtimes
+ /// that support debugging.
+ Future<bool> _loadThenPause(Stream<LoadSuite> suites) async {
+ var subscription = _suiteSubscription = suites.asyncMap((loadSuite) async {
+ var operation = _debugOperation = debug(_engine, _reporter, loadSuite);
+ await operation.valueOrCancellation();
+ }).listen(null);
+
+ var results = await Future.wait(<Future>[
+ subscription.asFuture<void>().then((_) => _engine.suiteSink.close()),
+ _engine.run()
+ ], eagerError: true);
+ return results.last as bool;
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/application_exception.dart b/pkgs/test_core/lib/src/runner/application_exception.dart
new file mode 100644
index 0000000..23155a2
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/application_exception.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2015, 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.
+
+/// An expected exception caused by user-controllable circumstances.
+class ApplicationException implements Exception {
+ final String message;
+
+ ApplicationException(this.message);
+
+ @override
+ String toString() => message;
+}
diff --git a/pkgs/test_core/lib/src/runner/compiler_pool.dart b/pkgs/test_core/lib/src/runner/compiler_pool.dart
new file mode 100644
index 0000000..52aa238
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/compiler_pool.dart
@@ -0,0 +1,57 @@
+// Copyright (c) 2015, 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 'package:async/async.dart';
+import 'package:meta/meta.dart';
+import 'package:pool/pool.dart';
+
+import 'configuration.dart';
+import 'suite.dart';
+
+/// A pool of compiler instances.
+///
+/// This limits the number of compiler instances running concurrently.
+abstract class CompilerPool {
+ /// The test runner configuration.
+ final config = Configuration.current;
+
+ /// The internal pool that controls the number of process running at once.
+ final Pool _pool;
+
+ /// Whether [close] has been called.
+ bool get closed => _closeMemo.hasRun;
+
+ /// The memoizer for running [close] exactly once.
+ final _closeMemo = AsyncMemoizer<void>();
+
+ /// Creates a compiler pool that multiple instances of a compiler at once.
+ CompilerPool() : _pool = Pool(Configuration.current.concurrency);
+
+ /// Compiles [code] to [path] using [_pool] and [compileInternal].
+ ///
+ /// Should not be overridden.
+ Future<void> compile(
+ String code, String path, SuiteConfiguration suiteConfig) =>
+ _pool.withResource(() {
+ if (closed) return null;
+ return compileInternal(code, path, suiteConfig);
+ });
+
+ /// The actual function a given compiler pool should implement to compile a
+ /// suite.
+ @protected
+ Future<void> compileInternal(
+ String code, String path, SuiteConfiguration suiteConfig);
+
+ /// Shuts down the compiler pool, invoking `closeInternal` exactly once.
+ ///
+ /// Should not be overridden.
+ Future<void> close() => _closeMemo.runOnce(closeInternal);
+
+ /// The actual function to shut down the compiler pool, invoked exactly once.
+ @protected
+ Future<void> closeInternal();
+}
diff --git a/pkgs/test_core/lib/src/runner/compiler_selection.dart b/pkgs/test_core/lib/src/runner/compiler_selection.dart
new file mode 100644
index 0000000..70ab553
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/compiler_selection.dart
@@ -0,0 +1,61 @@
+// 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 'package:source_span/source_span.dart';
+import 'package:test_api/backend.dart';
+
+/// A compiler with which the user has chosen to run tests.
+class CompilerSelection {
+ /// The chosen compiler to use.
+ final Compiler compiler;
+
+ /// The location in the configuration file of this compiler string, or `null`
+ /// if it was defined outside a configuration file (for example, on the
+ /// command line).
+ final SourceSpan? span;
+
+ /// The platform selector for which platforms this compiler should apply to,
+ /// if specified. Defaults to all platforms where the compiler is supported.
+ final PlatformSelector? platformSelector;
+
+ CompilerSelection(String compiler,
+ {required this.platformSelector, required this.span})
+ : compiler = Compiler.builtIn.firstWhere((c) => c.identifier == compiler);
+
+ factory CompilerSelection.parse(String option, {SourceSpan? parentSpan}) {
+ var parts = option.split(':');
+ switch (parts.length) {
+ case 1:
+ _checkValidCompiler(option, parentSpan);
+ return CompilerSelection(option,
+ platformSelector: null, span: parentSpan);
+ case 2:
+ var compiler = parts[1];
+ _checkValidCompiler(compiler, parentSpan);
+ return CompilerSelection(compiler,
+ platformSelector: PlatformSelector.parse(parts[0]),
+ span: parentSpan);
+ default:
+ throw ArgumentError.value(
+ option,
+ '--compiler',
+ 'Must be of the format [<boolean-selector>:]<compiler>, but got '
+ 'more than one `:`.');
+ }
+ }
+
+ @override
+ bool operator ==(Object other) =>
+ other is CompilerSelection && other.compiler == compiler;
+
+ @override
+ int get hashCode => compiler.hashCode;
+}
+
+void _checkValidCompiler(String compiler, SourceSpan? span) {
+ if (Compiler.builtIn.any((c) => c.identifier == compiler)) return;
+ throw SourceSpanFormatException(
+ 'Invalid compiler `$compiler`, must be one of ${Compiler.builtIn.map((c) => c.identifier).join(', ')}',
+ span);
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration.dart b/pkgs/test_core/lib/src/runner/configuration.dart
new file mode 100644
index 0000000..8ba4aa8
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration.dart
@@ -0,0 +1,1088 @@
+// Copyright (c) 2015, 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:io';
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:collection/collection.dart';
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as p;
+import 'package:source_span/source_span.dart';
+import 'package:test_api/scaffolding.dart' show Timeout;
+import 'package:test_api/src/backend/platform_selector.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+
+import '../util/io.dart';
+import 'compiler_selection.dart';
+import 'configuration/args.dart' as args;
+import 'configuration/custom_runtime.dart';
+import 'configuration/load.dart';
+import 'configuration/reporters.dart';
+import 'configuration/runtime_settings.dart';
+import 'configuration/utils.dart';
+import 'configuration/values.dart';
+import 'runtime_selection.dart';
+import 'suite.dart';
+
+export 'suite.dart' show TestSelection;
+
+/// The key used to look up [Configuration.current] in a zone.
+final _currentKey = Object();
+
+/// A class that encapsulates the command-line configuration of the test runner.
+class Configuration {
+ /// An empty configuration with only default values.
+ ///
+ /// Using this is slightly more efficient than manually constructing a new
+ /// configuration with no arguments.
+ static final empty = Configuration._unsafe();
+
+ /// The usage string for the command-line arguments.
+ static String get usage => args.usage;
+
+ /// Whether `--help` was passed.
+ bool get help => _help ?? false;
+ final bool? _help;
+
+ /// Custom HTML template file.
+ final String? customHtmlTemplatePath;
+
+ /// Whether `--version` was passed.
+ bool get version => _version ?? false;
+ final bool? _version;
+
+ /// Whether to pause for debugging after loading each test suite.
+ bool get pauseAfterLoad => _pauseAfterLoad ?? false;
+ final bool? _pauseAfterLoad;
+
+ /// Whether to run browsers in their respective debug modes
+ bool get debug => pauseAfterLoad || (_debug ?? false) || coverage != null;
+ final bool? _debug;
+
+ /// The output folder for coverage gathering
+ final String? coverage;
+
+ /// The path to the file from which to load more configuration information.
+ ///
+ /// This is *not* resolved automatically.
+ String get configurationPath => _configurationPath ?? 'dart_test.yaml';
+ final String? _configurationPath;
+
+ /// The name of the reporter to use to display results.
+ String get reporter => _reporter ?? defaultReporter;
+ final String? _reporter;
+
+ /// The map of file reporters where the key is the name of the reporter and
+ /// the value is the filepath to which its output should be written.
+ final Map<String, String> fileReporters;
+
+ /// Whether to disable retries of tests.
+ bool get noRetry => _noRetry ?? false;
+ final bool? _noRetry;
+
+ /// Whether to use command-line color escapes.
+ bool get color => _color ?? canUseSpecialChars;
+ final bool? _color;
+
+ /// How many tests to run concurrently.
+ int get concurrency =>
+ pauseAfterLoad ? 1 : (_concurrency ?? defaultConcurrency);
+ final int? _concurrency;
+
+ /// The index of the current shard, if sharding is in use, or `null` if it's
+ /// not.
+ ///
+ /// Sharding is a technique that allows the Google internal test framework to
+ /// easily split a test run across multiple workers without requiring the
+ /// tests to be modified by the user. When sharding is in use, the runner gets
+ /// a shard index (this field) and a total number of shards, and is expected
+ /// to provide the following guarantees:
+ ///
+ /// * Running the same invocation of the runner, with the same shard index and
+ /// total shards, will run the same set of tests.
+ /// * Across all shards, each test must be run exactly once.
+ ///
+ /// In addition, tests should be balanced across shards as much as possible.
+ final int? shardIndex;
+
+ /// The total number of shards, if sharding is in use, or `null` if it's not.
+ ///
+ /// See [shardIndex] for details.
+ final int? totalShards;
+
+ /// The list of packages to fold when producing [StackTrace]s.
+ Set<String> get foldTraceExcept => _foldTraceExcept ?? {};
+ final Set<String>? _foldTraceExcept;
+
+ /// If non-empty, all packages not in this list will be folded when producing
+ /// [StackTrace]s.
+ Set<String> get foldTraceOnly => _foldTraceOnly ?? {};
+ final Set<String>? _foldTraceOnly;
+
+ /// The paths from which to load tests, and the test cases to run.
+ Map<String, Set<TestSelection>> get testSelections =>
+ _testSelections ??
+ const {
+ 'test': {TestSelection()}
+ };
+ final Map<String, Set<TestSelection>>? _testSelections;
+
+ /// Whether the load paths were passed explicitly or the default was used.
+ bool get explicitPaths => _testSelections != null;
+
+ /// The glob matching the basename of tests to run.
+ ///
+ /// This is used to find tests within a directory.
+ Glob get filename => _filename ?? defaultFilename;
+ final Glob? _filename;
+
+ /// The set of presets to use.
+ ///
+ /// Any chosen presets for the parent configuration are added to the chosen
+ /// preset sets for child configurations as well.
+ ///
+ /// Note that the order of this set matters.
+ final Set<String> chosenPresets;
+
+ /// The set of tags that have been declared in any way in this configuration.
+ late final Set<String> knownTags = UnmodifiableSetView({
+ ...includeTags.variables,
+ ...excludeTags.variables,
+ ...suiteDefaults.knownTags,
+ for (var configuration in presets.values) ...configuration.knownTags
+ });
+
+ /// Only run tests whose tags match this selector.
+ ///
+ /// When [merge]d, this is intersected with the other configuration's included
+ /// tags.
+ final BooleanSelector includeTags;
+
+ /// Do not run tests whose tags match this selector.
+ ///
+ /// When [merge]d, this is unioned with the other configuration's
+ /// excluded tags.
+ final BooleanSelector excludeTags;
+
+ /// Configuration presets.
+ ///
+ /// These are configurations that can be explicitly selected by the user via
+ /// the command line. Preset configuration takes precedence over the base
+ /// configuration.
+ ///
+ /// This is guaranteed not to have any keys that match [chosenPresets]; those
+ /// are resolved when the configuration is constructed.
+ final Map<String, Configuration> presets;
+
+ /// All preset names that are known to be valid.
+ ///
+ /// This includes presets that have already been resolved.
+ Set<String> get knownPresets => _knownPresets ??= UnmodifiableSetView({
+ ...presets.keys,
+ for (var configuration in presets.values) ...configuration.knownPresets
+ });
+ Set<String>? _knownPresets;
+
+ /// Built-in runtimes whose settings are overridden by the user.
+ final Map<String, RuntimeSettings> overrideRuntimes;
+
+ /// Runtimes defined by the user in terms of existing runtimes.
+ final Map<String, CustomRuntime> defineRuntimes;
+
+ /// The default suite-level configuration.
+ final SuiteConfiguration suiteDefaults;
+
+ /// The set of patterns to check test names against in all suites that run.
+ final Set<Pattern> globalPatterns;
+
+ /// The seed used to generate randomness for test case shuffling.
+ ///
+ /// If null or zero no shuffling will occur.
+ /// The same seed will shuffle the tests in the same way every time.
+ final int? testRandomizeOrderingSeed;
+
+ final bool? _stopOnFirstFailure;
+
+ /// Whether to stop running subsequent tests after a test fails.
+ bool get stopOnFirstFailure => _stopOnFirstFailure ?? false;
+
+ /// Returns the current configuration, or a default configuration if no
+ /// current configuration is set.
+ ///
+ /// The current configuration is set using [asCurrent].
+ static Configuration get current =>
+ Zone.current[_currentKey] as Configuration? ?? Configuration._unsafe();
+
+ /// Parses the configuration from [args].
+ ///
+ /// Throws a [FormatException] if [args] are invalid.
+ factory Configuration.parse(List<String> arguments) => args.parse(arguments);
+
+ /// Loads configuration from [path].
+ ///
+ /// If [global] is `true`, this restricts the configuration to rules that are
+ /// supported globally.
+ ///
+ /// Throws an [IOException] if [path] does not exist or cannot be read. Throws
+ /// a [FormatException] if the file contents are invalid.
+ factory Configuration.load(String path, {bool global = false}) {
+ final content = File(path).readAsStringSync();
+ final sourceUrl = p.toUri(path);
+ return parse(content, global: global, sourceUrl: sourceUrl);
+ }
+
+ /// Parses configuration from YAML formatted [content].
+ ///
+ /// If [global] is `true`, this restricts the configuration to rules that are
+ /// supported globally.
+ ///
+ /// If [sourceUrl] is provided it will be set as the source url for the yaml
+ /// document.
+ ///
+ /// Throws a [FormatException] if the content is invalid.
+ factory Configuration.loadFromString(String content,
+ {bool global = false, Uri? sourceUrl}) =>
+ parse(content, global: global, sourceUrl: sourceUrl);
+
+ factory Configuration(
+ {required bool? help,
+ required String? customHtmlTemplatePath,
+ required bool? version,
+ required bool? pauseAfterLoad,
+ required bool? debug,
+ required bool? color,
+ required String? configurationPath,
+ required String? reporter,
+ required Map<String, String>? fileReporters,
+ required String? coverage,
+ required int? concurrency,
+ required int? shardIndex,
+ required int? totalShards,
+ required Map<String, Set<TestSelection>>? testSelections,
+ required Iterable<String>? foldTraceExcept,
+ required Iterable<String>? foldTraceOnly,
+ required Glob? filename,
+ required Iterable<String>? chosenPresets,
+ required Map<String, Configuration>? presets,
+ required Map<String, RuntimeSettings>? overrideRuntimes,
+ required Map<String, CustomRuntime>? defineRuntimes,
+ required bool? noRetry,
+ required int? testRandomizeOrderingSeed,
+ required bool? stopOnFirstFailure,
+
+ // Suite-level configuration
+ required bool? allowDuplicateTestNames,
+ required bool? allowTestRandomization,
+ required bool? jsTrace,
+ required bool? runSkipped,
+ required Iterable<String>? dart2jsArgs,
+ required String? precompiledPath,
+ required Iterable<Pattern>? globalPatterns,
+ required Iterable<CompilerSelection>? compilerSelections,
+ required Iterable<RuntimeSelection>? runtimes,
+ required BooleanSelector? includeTags,
+ required BooleanSelector? excludeTags,
+ required Map<BooleanSelector, SuiteConfiguration>? tags,
+ required Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ required bool? ignoreTimeouts,
+
+ // Test-level configuration
+ required Timeout? timeout,
+ required bool? verboseTrace,
+ required bool? chainStackTraces,
+ required bool? skip,
+ required int? retry,
+ required String? skipReason,
+ required PlatformSelector? testOn,
+ required Iterable<String>? addTags}) {
+ var chosenPresetSet = chosenPresets?.toSet();
+ var configuration = Configuration._(
+ help: help,
+ customHtmlTemplatePath: customHtmlTemplatePath,
+ version: version,
+ pauseAfterLoad: pauseAfterLoad,
+ debug: debug,
+ color: color,
+ configurationPath: configurationPath,
+ reporter: reporter,
+ fileReporters: fileReporters,
+ coverage: coverage,
+ concurrency: concurrency,
+ shardIndex: shardIndex,
+ totalShards: totalShards,
+ testSelections: testSelections,
+ foldTraceExcept: foldTraceExcept,
+ foldTraceOnly: foldTraceOnly,
+ filename: filename,
+ chosenPresets: chosenPresetSet,
+ presets: _withChosenPresets(presets, chosenPresetSet),
+ overrideRuntimes: overrideRuntimes,
+ defineRuntimes: defineRuntimes,
+ noRetry: noRetry,
+ testRandomizeOrderingSeed: testRandomizeOrderingSeed,
+ stopOnFirstFailure: stopOnFirstFailure,
+ includeTags: includeTags,
+ excludeTags: excludeTags,
+ globalPatterns: globalPatterns,
+ suiteDefaults: SuiteConfiguration(
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ allowTestRandomization: allowTestRandomization,
+ jsTrace: jsTrace,
+ runSkipped: runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ precompiledPath: precompiledPath,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ tags: tags,
+ onPlatform: onPlatform,
+ ignoreTimeouts: ignoreTimeouts,
+
+ // Test-level configuration
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags));
+ return configuration._resolvePresets();
+ }
+
+ /// A constructor that doesn't require all of its options to be passed.
+ ///
+ /// This should only be used in situations where you really only want to
+ /// configure a specific restricted set of options.
+ factory Configuration._unsafe(
+ {bool? help,
+ String? customHtmlTemplatePath,
+ bool? version,
+ bool? pauseAfterLoad,
+ bool? debug,
+ bool? color,
+ String? configurationPath,
+ String? reporter,
+ Map<String, String>? fileReporters,
+ String? coverage,
+ int? concurrency,
+ int? shardIndex,
+ int? totalShards,
+ Map<String, Set<TestSelection>>? testSelections,
+ Iterable<String>? foldTraceExcept,
+ Iterable<String>? foldTraceOnly,
+ Glob? filename,
+ Iterable<String>? chosenPresets,
+ Map<String, Configuration>? presets,
+ Map<String, RuntimeSettings>? overrideRuntimes,
+ Map<String, CustomRuntime>? defineRuntimes,
+ bool? noRetry,
+ int? testRandomizeOrderingSeed,
+ bool? stopOnFirstFailure,
+
+ // Suite-level configuration
+ bool? allowDuplicateTestNames,
+ bool? allowTestRandomization,
+ bool? jsTrace,
+ bool? runSkipped,
+ Iterable<String>? dart2jsArgs,
+ String? precompiledPath,
+ Iterable<Pattern>? globalPatterns,
+ Iterable<CompilerSelection>? compilerSelections,
+ Iterable<RuntimeSelection>? runtimes,
+ BooleanSelector? includeTags,
+ BooleanSelector? excludeTags,
+ Map<BooleanSelector, SuiteConfiguration>? tags,
+ Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ bool? ignoreTimeouts,
+
+ // Test-level configuration
+ Timeout? timeout,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ bool? skip,
+ int? retry,
+ String? skipReason,
+ PlatformSelector? testOn,
+ Iterable<String>? addTags}) =>
+ Configuration(
+ help: help,
+ customHtmlTemplatePath: customHtmlTemplatePath,
+ version: version,
+ pauseAfterLoad: pauseAfterLoad,
+ debug: debug,
+ color: color,
+ configurationPath: configurationPath,
+ reporter: reporter,
+ fileReporters: fileReporters,
+ coverage: coverage,
+ concurrency: concurrency,
+ shardIndex: shardIndex,
+ totalShards: totalShards,
+ testSelections: testSelections,
+ foldTraceExcept: foldTraceExcept,
+ foldTraceOnly: foldTraceOnly,
+ filename: filename,
+ chosenPresets: chosenPresets,
+ presets: presets,
+ overrideRuntimes: overrideRuntimes,
+ defineRuntimes: defineRuntimes,
+ noRetry: noRetry,
+ testRandomizeOrderingSeed: testRandomizeOrderingSeed,
+ stopOnFirstFailure: stopOnFirstFailure,
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ allowTestRandomization: allowTestRandomization,
+ jsTrace: jsTrace,
+ runSkipped: runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ precompiledPath: precompiledPath,
+ globalPatterns: globalPatterns,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ includeTags: includeTags,
+ excludeTags: excludeTags,
+ tags: tags,
+ onPlatform: onPlatform,
+ ignoreTimeouts: ignoreTimeouts,
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags);
+
+ /// Suite level configuration allowed in the global test config file.
+ ///
+ /// This is per-user configuration and should be limited as such, it should
+ /// not contain options that would change the pass/fail result of any given
+ /// test, or change which tests would run.
+ factory Configuration.globalTest({
+ required bool? verboseTrace,
+ required bool? jsTrace,
+ required Timeout? timeout,
+ required Map<String, Configuration>? presets,
+ required bool? chainStackTraces,
+ required Iterable<String>? foldTraceExcept,
+ required Iterable<String>? foldTraceOnly,
+ }) =>
+ Configuration(
+ foldTraceExcept: foldTraceExcept,
+ foldTraceOnly: foldTraceOnly,
+ jsTrace: jsTrace,
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ help: null,
+ customHtmlTemplatePath: null,
+ version: null,
+ pauseAfterLoad: null,
+ debug: null,
+ color: null,
+ configurationPath: null,
+ reporter: null,
+ fileReporters: null,
+ coverage: null,
+ concurrency: null,
+ shardIndex: null,
+ totalShards: null,
+ testSelections: null,
+ filename: null,
+ chosenPresets: null,
+ presets: presets,
+ overrideRuntimes: null,
+ defineRuntimes: null,
+ noRetry: null,
+ testRandomizeOrderingSeed: null,
+ stopOnFirstFailure: null,
+ ignoreTimeouts: null,
+ allowDuplicateTestNames: null,
+ allowTestRandomization: null,
+ runSkipped: null,
+ dart2jsArgs: null,
+ precompiledPath: null,
+ globalPatterns: null,
+ compilerSelections: null,
+ runtimes: null,
+ includeTags: null,
+ excludeTags: null,
+ tags: null,
+ onPlatform: null,
+ skip: null,
+ retry: null,
+ skipReason: null,
+ testOn: null,
+ addTags: null,
+ );
+
+ /// Suite level configuration that is not allowed in the global test
+ /// config file.
+ ///
+ /// This configuration may alter the pass/fail result of a test run, and thus
+ /// should only be configured per package and not at the global level (global
+ /// config is user specific).
+ factory Configuration.localTest({
+ required bool? skip,
+ required int? retry,
+ required String? skipReason,
+ required PlatformSelector? testOn,
+ required Iterable<String>? addTags,
+ required bool? allowDuplicateTestNames,
+ required bool? allowTestRandomization,
+ }) =>
+ Configuration(
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ allowTestRandomization: allowTestRandomization,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags,
+ help: null,
+ customHtmlTemplatePath: null,
+ version: null,
+ pauseAfterLoad: null,
+ debug: null,
+ color: null,
+ configurationPath: null,
+ reporter: null,
+ fileReporters: null,
+ coverage: null,
+ concurrency: null,
+ shardIndex: null,
+ totalShards: null,
+ testSelections: null,
+ foldTraceExcept: null,
+ foldTraceOnly: null,
+ filename: null,
+ chosenPresets: null,
+ presets: null,
+ overrideRuntimes: null,
+ defineRuntimes: null,
+ noRetry: null,
+ testRandomizeOrderingSeed: null,
+ stopOnFirstFailure: null,
+ jsTrace: null,
+ runSkipped: null,
+ dart2jsArgs: null,
+ precompiledPath: null,
+ globalPatterns: null,
+ compilerSelections: null,
+ runtimes: null,
+ includeTags: null,
+ excludeTags: null,
+ tags: null,
+ onPlatform: null,
+ ignoreTimeouts: null,
+ timeout: null,
+ verboseTrace: null,
+ chainStackTraces: null,
+ );
+
+ /// Runner configuration that is allowed in the global test config file.
+ ///
+ /// This is per-user configuration and should be limited as such, it should
+ /// not contain options that would change the pass/fail result of any given
+ /// test, or change which tests would run.
+ ///
+ /// Note that [customHtmlTemplatePath] violates this rule, and really should
+ /// not be configurable globally.
+ factory Configuration.globalRunner(
+ {required bool? pauseAfterLoad,
+ required String? customHtmlTemplatePath,
+ required bool? runSkipped,
+ required String? reporter,
+ required Map<String, String>? fileReporters,
+ required int? concurrency,
+ required Iterable<CompilerSelection>? compilerSelections,
+ required Iterable<RuntimeSelection>? runtimes,
+ required Iterable<String>? chosenPresets,
+ required Map<String, RuntimeSettings>? overrideRuntimes}) =>
+ Configuration(
+ customHtmlTemplatePath: customHtmlTemplatePath,
+ pauseAfterLoad: pauseAfterLoad,
+ runSkipped: runSkipped,
+ reporter: reporter,
+ fileReporters: fileReporters,
+ concurrency: concurrency,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ chosenPresets: chosenPresets,
+ overrideRuntimes: overrideRuntimes,
+ help: null,
+ version: null,
+ debug: null,
+ color: null,
+ configurationPath: null,
+ coverage: null,
+ shardIndex: null,
+ totalShards: null,
+ testSelections: null,
+ foldTraceExcept: null,
+ foldTraceOnly: null,
+ filename: null,
+ presets: null,
+ defineRuntimes: null,
+ noRetry: null,
+ testRandomizeOrderingSeed: null,
+ stopOnFirstFailure: null,
+ allowDuplicateTestNames: null,
+ allowTestRandomization: null,
+ jsTrace: null,
+ dart2jsArgs: null,
+ precompiledPath: null,
+ globalPatterns: null,
+ includeTags: null,
+ excludeTags: null,
+ tags: null,
+ onPlatform: null,
+ ignoreTimeouts: null,
+ timeout: null,
+ verboseTrace: null,
+ chainStackTraces: null,
+ skip: null,
+ retry: null,
+ skipReason: null,
+ testOn: null,
+ addTags: null,
+ );
+
+ /// Runner configuration that is not allowed in the global test config file.
+ ///
+ /// This configuration may alter the pass/fail result of a test run, and thus
+ /// should only be configured per package and not at the global level (global
+ /// config is user specific).
+ factory Configuration.localRunner(
+ {required Iterable<Pattern>? globalPatterns,
+ required Map<String, Set<TestSelection>>? testSelections,
+ required Glob? filename,
+ required BooleanSelector? includeTags,
+ required BooleanSelector? excludeTags,
+ required Map<String, CustomRuntime>? defineRuntimes}) =>
+ Configuration(
+ globalPatterns: globalPatterns,
+ testSelections: testSelections,
+ filename: filename,
+ includeTags: includeTags,
+ excludeTags: excludeTags,
+ defineRuntimes: defineRuntimes,
+ help: null,
+ customHtmlTemplatePath: null,
+ version: null,
+ pauseAfterLoad: null,
+ debug: null,
+ color: null,
+ configurationPath: null,
+ reporter: null,
+ fileReporters: null,
+ coverage: null,
+ concurrency: null,
+ shardIndex: null,
+ totalShards: null,
+ foldTraceExcept: null,
+ foldTraceOnly: null,
+ chosenPresets: null,
+ presets: null,
+ overrideRuntimes: null,
+ noRetry: null,
+ testRandomizeOrderingSeed: null,
+ stopOnFirstFailure: null,
+ allowDuplicateTestNames: null,
+ allowTestRandomization: null,
+ jsTrace: null,
+ runSkipped: null,
+ dart2jsArgs: null,
+ precompiledPath: null,
+ compilerSelections: null,
+ runtimes: null,
+ tags: null,
+ onPlatform: null,
+ ignoreTimeouts: null,
+ timeout: null,
+ verboseTrace: null,
+ chainStackTraces: null,
+ skip: null,
+ retry: null,
+ skipReason: null,
+ testOn: null,
+ addTags: null);
+
+ /// A specialized constructor for configuring only `onPlatform`.
+ factory Configuration.onPlatform(
+ Map<PlatformSelector, SuiteConfiguration> onPlatform) =>
+ Configuration._unsafe(onPlatform: onPlatform);
+
+ factory Configuration.tags(Map<BooleanSelector, SuiteConfiguration> tags) =>
+ Configuration._unsafe(tags: tags);
+
+ static Map<String, Configuration>? _withChosenPresets(
+ Map<String, Configuration>? map, Set<String>? chosenPresets) {
+ if (map == null || chosenPresets == null) return map;
+ return map.map((key, config) => MapEntry(
+ key,
+ config.change(
+ chosenPresets: config.chosenPresets.union(chosenPresets))));
+ }
+
+ /// Creates new Configuration.
+ ///
+ /// Unlike [Configuration.new], this assumes [presets] is already resolved.
+ Configuration._(
+ {required bool? help,
+ required this.customHtmlTemplatePath,
+ required bool? version,
+ required bool? pauseAfterLoad,
+ required bool? debug,
+ required bool? color,
+ required String? configurationPath,
+ required String? reporter,
+ required Map<String, String>? fileReporters,
+ required this.coverage,
+ required int? concurrency,
+ required this.shardIndex,
+ required this.totalShards,
+ required Map<String, Set<TestSelection>>? testSelections,
+ required Iterable<String>? foldTraceExcept,
+ required Iterable<String>? foldTraceOnly,
+ required Glob? filename,
+ required Iterable<String>? chosenPresets,
+ required Map<String, Configuration>? presets,
+ required Map<String, RuntimeSettings>? overrideRuntimes,
+ required Map<String, CustomRuntime>? defineRuntimes,
+ required bool? noRetry,
+ required this.testRandomizeOrderingSeed,
+ required bool? stopOnFirstFailure,
+ required BooleanSelector? includeTags,
+ required BooleanSelector? excludeTags,
+ required Iterable<Pattern>? globalPatterns,
+ required SuiteConfiguration? suiteDefaults})
+ : _help = help,
+ _version = version,
+ _pauseAfterLoad = pauseAfterLoad,
+ _debug = debug,
+ _color = color,
+ _configurationPath = configurationPath,
+ _reporter = reporter,
+ fileReporters = fileReporters ?? {},
+ _concurrency = concurrency,
+ _testSelections = testSelections == null || testSelections.isEmpty
+ ? null
+ : Map.unmodifiable(testSelections),
+ _foldTraceExcept = _set(foldTraceExcept),
+ _foldTraceOnly = _set(foldTraceOnly),
+ _filename = filename,
+ chosenPresets = UnmodifiableSetView(chosenPresets?.toSet() ?? {}),
+ presets = _map(presets),
+ overrideRuntimes = _map(overrideRuntimes),
+ defineRuntimes = _map(defineRuntimes),
+ _noRetry = noRetry,
+ includeTags = includeTags ?? BooleanSelector.all,
+ excludeTags = excludeTags ?? BooleanSelector.none,
+ globalPatterns = globalPatterns == null
+ ? const {}
+ : UnmodifiableSetView(globalPatterns.toSet()),
+ _stopOnFirstFailure = stopOnFirstFailure,
+ suiteDefaults = (() {
+ var config = suiteDefaults ?? SuiteConfiguration.empty;
+ if (pauseAfterLoad == true) {
+ return config.change(ignoreTimeouts: true);
+ }
+ return config;
+ }()) {
+ if (_filename != null && _filename.context.style != p.style) {
+ throw ArgumentError(
+ "filename's context must match the current operating system, was "
+ '${_filename.context.style}.');
+ }
+
+ if ((shardIndex == null) != (totalShards == null)) {
+ throw ArgumentError(
+ 'shardIndex and totalShards may only be passed together.');
+ } else if (shardIndex != null) {
+ RangeError.checkValueInInterval(
+ shardIndex!, 0, totalShards! - 1, 'shardIndex');
+ }
+ }
+
+ /// Creates a new [Configuration] that takes its configuration from
+ /// [SuiteConfiguration].
+ factory Configuration.fromSuiteConfiguration(
+ SuiteConfiguration suiteConfig) =>
+ Configuration._(
+ suiteDefaults: suiteConfig,
+ globalPatterns: null,
+ help: null,
+ customHtmlTemplatePath: null,
+ version: null,
+ pauseAfterLoad: null,
+ debug: null,
+ color: null,
+ configurationPath: null,
+ reporter: null,
+ fileReporters: null,
+ coverage: null,
+ concurrency: null,
+ shardIndex: null,
+ totalShards: null,
+ testSelections: null,
+ foldTraceExcept: null,
+ foldTraceOnly: null,
+ filename: null,
+ chosenPresets: null,
+ presets: null,
+ overrideRuntimes: null,
+ defineRuntimes: null,
+ noRetry: null,
+ testRandomizeOrderingSeed: null,
+ stopOnFirstFailure: null,
+ includeTags: null,
+ excludeTags: null,
+ );
+
+ /// Returns a set from [input].
+ ///
+ /// If [input] is `null` or empty, this returns `null`.
+ static Set<T>? _set<T>(Iterable<T>? input) {
+ if (input == null) return null;
+ var set = Set<T>.from(input);
+ if (set.isEmpty) return null;
+ return set;
+ }
+
+ /// Returns an unmodifiable copy of [input] or an empty unmodifiable map.
+ static Map<K, V> _map<K, V>(Map<K, V>? input) {
+ input ??= {};
+ return Map.unmodifiable(input);
+ }
+
+ /// Runs [body] with this as [Configuration.current].
+ ///
+ /// This is zone-scoped, so this will be the current configuration in any
+ /// asynchronous callbacks transitively created by [body].
+ T asCurrent<T>(T Function() body) =>
+ runZoned(body, zoneValues: {_currentKey: this});
+
+ /// Throws a [FormatException] if this refers to any undefined runtimes.
+ void validateRuntimes(List<Runtime> allRuntimes) {
+ // We don't need to verify [customRuntimes] here because those runtimes
+ // already need to be verified and resolved to create [allRuntimes].
+
+ for (var settings in overrideRuntimes.values) {
+ if (!allRuntimes
+ .any((runtime) => runtime.identifier == settings.identifier)) {
+ throw SourceSpanFormatException(
+ 'Unknown platform "${settings.identifier}".',
+ settings.identifierSpan);
+ }
+ }
+
+ suiteDefaults.validateRuntimes(allRuntimes);
+ for (var config in presets.values) {
+ config.validateRuntimes(allRuntimes);
+ }
+ }
+
+ /// Merges this with [other].
+ ///
+ /// For most fields, if both configurations have values set, [other]'s value
+ /// takes precedence. However, certain fields are merged together instead.
+ /// This is indicated in those fields' documentation.
+ Configuration merge(Configuration other) {
+ if (this == Configuration.empty) return other;
+ if (other == Configuration.empty) return this;
+
+ var foldTraceOnly = other._foldTraceOnly ?? _foldTraceOnly;
+ var foldTraceExcept = other._foldTraceExcept ?? _foldTraceExcept;
+ if (_foldTraceOnly != null) {
+ if (other._foldTraceExcept != null) {
+ foldTraceOnly = _foldTraceOnly.difference(other._foldTraceExcept);
+ } else if (other._foldTraceOnly != null) {
+ foldTraceOnly = other._foldTraceOnly.intersection(_foldTraceOnly);
+ }
+ } else if (_foldTraceExcept != null) {
+ if (other._foldTraceOnly != null) {
+ foldTraceOnly = other._foldTraceOnly.difference(_foldTraceExcept);
+ } else if (other._foldTraceExcept != null) {
+ foldTraceExcept = other._foldTraceExcept.union(_foldTraceExcept);
+ }
+ }
+
+ var result = Configuration._(
+ help: other._help ?? _help,
+ customHtmlTemplatePath:
+ other.customHtmlTemplatePath ?? customHtmlTemplatePath,
+ version: other._version ?? _version,
+ pauseAfterLoad: other._pauseAfterLoad ?? _pauseAfterLoad,
+ debug: other._debug ?? _debug,
+ color: other._color ?? _color,
+ configurationPath: other._configurationPath ?? _configurationPath,
+ reporter: other._reporter ?? _reporter,
+ fileReporters: mergeMaps(fileReporters, other.fileReporters),
+ coverage: other.coverage ?? coverage,
+ concurrency: other._concurrency ?? _concurrency,
+ shardIndex: other.shardIndex ?? shardIndex,
+ totalShards: other.totalShards ?? totalShards,
+ testSelections: other._testSelections ?? _testSelections,
+ foldTraceExcept: foldTraceExcept,
+ foldTraceOnly: foldTraceOnly,
+ filename: other._filename ?? _filename,
+ chosenPresets: chosenPresets.union(other.chosenPresets),
+ presets: _mergeConfigMaps(presets, other.presets),
+ overrideRuntimes: mergeUnmodifiableMaps(
+ overrideRuntimes, other.overrideRuntimes,
+ value: (settings1, settings2) => RuntimeSettings(
+ settings1.identifier,
+ settings1.identifierSpan,
+ [...settings1.settings, ...settings2.settings])),
+ defineRuntimes:
+ mergeUnmodifiableMaps(defineRuntimes, other.defineRuntimes),
+ noRetry: other._noRetry ?? _noRetry,
+ testRandomizeOrderingSeed:
+ other.testRandomizeOrderingSeed ?? testRandomizeOrderingSeed,
+ stopOnFirstFailure: other._stopOnFirstFailure ?? _stopOnFirstFailure,
+ includeTags: includeTags.intersection(other.includeTags),
+ excludeTags: excludeTags.union(other.excludeTags),
+ globalPatterns: globalPatterns.union(other.globalPatterns),
+ suiteDefaults: suiteDefaults.merge(other.suiteDefaults));
+ result = result._resolvePresets();
+
+ // Make sure the merged config preserves any presets that were chosen and
+ // discarded.
+ result._knownPresets = knownPresets.union(other.knownPresets);
+ return result;
+ }
+
+ /// Returns a copy of this configuration with the given fields updated.
+ ///
+ /// Note that unlike [merge], this has no merging behavior—the old value is
+ /// always replaced by the new one.
+ Configuration change(
+ {bool? help,
+ String? customHtmlTemplatePath,
+ bool? version,
+ bool? pauseAfterLoad,
+ bool? debug,
+ bool? color,
+ String? configurationPath,
+ String? reporter,
+ Map<String, String>? fileReporters,
+ String? coverage,
+ int? concurrency,
+ int? shardIndex,
+ int? totalShards,
+ Map<String, Set<TestSelection>>? testSelections,
+ Iterable<String>? exceptPackages,
+ Iterable<String>? onlyPackages,
+ Glob? filename,
+ Iterable<String>? chosenPresets,
+ Map<String, Configuration>? presets,
+ Map<String, RuntimeSettings>? overrideRuntimes,
+ Map<String, CustomRuntime>? defineRuntimes,
+ bool? noRetry,
+ int? testRandomizeOrderingSeed,
+ bool? ignoreTimeouts,
+
+ // Suite-level configuration
+ bool? allowDuplicateTestNames,
+ bool? jsTrace,
+ bool? runSkipped,
+ Iterable<String>? dart2jsArgs,
+ String? precompiledPath,
+ Iterable<RuntimeSelection>? runtimes,
+ BooleanSelector? includeTags,
+ BooleanSelector? excludeTags,
+ Map<BooleanSelector, SuiteConfiguration>? tags,
+ Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+
+ // Test-level configuration
+ Timeout? timeout,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ bool? skip,
+ String? skipReason,
+ PlatformSelector? testOn,
+ Iterable<String>? addTags}) {
+ var config = Configuration._(
+ help: help ?? _help,
+ customHtmlTemplatePath:
+ customHtmlTemplatePath ?? this.customHtmlTemplatePath,
+ version: version ?? _version,
+ pauseAfterLoad: pauseAfterLoad ?? _pauseAfterLoad,
+ debug: debug ?? _debug,
+ color: color ?? _color,
+ configurationPath: configurationPath ?? _configurationPath,
+ reporter: reporter ?? _reporter,
+ fileReporters: fileReporters ?? this.fileReporters,
+ coverage: coverage ?? this.coverage,
+ concurrency: concurrency ?? _concurrency,
+ shardIndex: shardIndex ?? this.shardIndex,
+ totalShards: totalShards ?? this.totalShards,
+ testSelections: testSelections ?? _testSelections,
+ foldTraceExcept: exceptPackages ?? _foldTraceExcept,
+ foldTraceOnly: onlyPackages ?? _foldTraceOnly,
+ filename: filename ?? _filename,
+ chosenPresets: chosenPresets ?? this.chosenPresets,
+ presets: presets ?? this.presets,
+ overrideRuntimes: overrideRuntimes ?? this.overrideRuntimes,
+ defineRuntimes: defineRuntimes ?? this.defineRuntimes,
+ noRetry: noRetry ?? _noRetry,
+ testRandomizeOrderingSeed:
+ testRandomizeOrderingSeed ?? this.testRandomizeOrderingSeed,
+ stopOnFirstFailure: _stopOnFirstFailure,
+ includeTags: includeTags,
+ excludeTags: excludeTags,
+ globalPatterns: globalPatterns,
+ suiteDefaults: suiteDefaults.change(
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ jsTrace: jsTrace,
+ runSkipped: runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ precompiledPath: precompiledPath,
+ runtimes: runtimes,
+ tags: tags,
+ onPlatform: onPlatform,
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags,
+ ignoreTimeouts: ignoreTimeouts,
+ ));
+ return config._resolvePresets();
+ }
+
+ /// Merges two maps whose values are [Configuration]s.
+ ///
+ /// Any overlapping keys in the maps have their configurations merged in the
+ /// returned map.
+ Map<String, Configuration> _mergeConfigMaps(
+ Map<String, Configuration> map1, Map<String, Configuration> map2) =>
+ mergeMaps(map1, map2,
+ value: (config1, config2) => config1.merge(config2));
+
+ /// Returns a copy of this [Configuration] with all [chosenPresets] resolved
+ /// against [presets].
+ Configuration _resolvePresets() {
+ if (chosenPresets.isEmpty || presets.isEmpty) return this;
+
+ var newPresets = Map<String, Configuration>.from(presets);
+ var merged = chosenPresets.fold(
+ empty,
+ (Configuration merged, preset) =>
+ merged.merge(newPresets.remove(preset) ?? Configuration.empty));
+
+ if (merged == empty) return this;
+ var result = change(presets: newPresets).merge(merged);
+
+ // Make sure the configuration knows about presets that were selected and
+ // thus removed from [newPresets].
+ result._knownPresets =
+ UnmodifiableSetView({...result.knownPresets, ...presets.keys});
+
+ return result;
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration/args.dart b/pkgs/test_core/lib/src/runner/configuration/args.dart
new file mode 100644
index 0000000..e981bed
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration/args.dart
@@ -0,0 +1,426 @@
+// Copyright (c) 2016, 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:io';
+import 'dart:math';
+
+import 'package:args/args.dart';
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:test_api/backend.dart';
+import 'package:test_api/scaffolding.dart' show Timeout;
+
+import '../../util/io.dart';
+import '../compiler_selection.dart';
+import '../configuration.dart';
+import '../runtime_selection.dart';
+import 'reporters.dart';
+import 'values.dart';
+
+/// The parser used to parse the command-line arguments.
+final ArgParser _parser = (() {
+ var parser = ArgParser(allowTrailingOptions: true);
+
+ var allRuntimes = Runtime.builtIn.toList()..remove(Runtime.vm);
+ if (!Platform.isMacOS) allRuntimes.remove(Runtime.safari);
+
+ parser.addFlag('help',
+ abbr: 'h', negatable: false, help: 'Show this usage information.');
+ parser.addFlag('version',
+ negatable: false, help: 'Show the package:test version.');
+
+ // Note that defaultsTo declarations here are only for documentation purposes.
+ // We pass null instead of the default so that it merges properly with the
+ // config file.
+
+ parser.addSeparator('Selecting Tests:');
+ parser.addMultiOption('name',
+ abbr: 'n',
+ help: 'A substring of the name of the test to run.\n'
+ 'Regular expression syntax is supported.\n'
+ 'If passed multiple times, tests must match all substrings.',
+ splitCommas: false);
+ parser.addMultiOption('plain-name',
+ abbr: 'N',
+ help: 'A plain-text substring of the name of the test to run.\n'
+ 'If passed multiple times, tests must match all substrings.',
+ splitCommas: false);
+ parser.addMultiOption('tags',
+ abbr: 't',
+ help: 'Run only tests with all of the specified tags.\n'
+ 'Supports boolean selector syntax.');
+ parser.addMultiOption('tag', hide: true);
+ parser.addMultiOption('exclude-tags',
+ abbr: 'x',
+ help: "Don't run tests with any of the specified tags.\n"
+ 'Supports boolean selector syntax.');
+ parser.addMultiOption('exclude-tag', hide: true);
+ parser.addFlag('run-skipped',
+ help: 'Run skipped tests instead of skipping them.');
+
+ parser.addSeparator('Running Tests:');
+
+ // The UI term "platform" corresponds with the implementation term "runtime".
+ // The [Runtime] class used to be called [TestPlatform], but it was changed to
+ // avoid conflicting with [SuitePlatform]. We decided not to also change the
+ // UI to avoid a painful migration.
+ parser.addMultiOption('platform',
+ abbr: 'p',
+ help: 'The platform(s) on which to run the tests.\n'
+ '[vm (default), '
+ '${allRuntimes.map((runtime) => runtime.identifier).join(", ")}].\n'
+ 'Each platform supports the following compilers:\n'
+ '${Runtime.vm.supportedCompilersText}\n'
+ '${allRuntimes.map((r) => r.supportedCompilersText).join('\n')}');
+ parser.addMultiOption('compiler',
+ abbr: 'c',
+ help: 'The compiler(s) to use to run tests, supported compilers are '
+ '[${Compiler.builtIn.map((c) => c.identifier).join(', ')}].\n'
+ 'Each platform has a default compiler but may support other '
+ 'compilers.\n'
+ 'You can target a compiler to a specific platform using arguments '
+ 'of the following form [<platform-selector>:]<compiler>.\n'
+ 'If a platform is specified but no given compiler is supported for '
+ 'that platform, then it will use its default compiler.');
+ parser.addMultiOption('preset',
+ abbr: 'P', help: 'The configuration preset(s) to use.');
+ parser.addOption('concurrency',
+ abbr: 'j',
+ help: 'The number of concurrent test suites run.',
+ defaultsTo: defaultConcurrency.toString(),
+ valueHelp: 'threads');
+ parser.addOption('total-shards',
+ help: 'The total number of invocations of the test runner being run.');
+ parser.addOption('shard-index',
+ help: 'The index of this test runner invocation (of --total-shards).');
+ parser.addOption('pub-serve',
+ help: '[Removed] The port of a pub serve instance serving "test/".',
+ valueHelp: 'port',
+ hide: true);
+ parser.addOption('timeout',
+ help: 'The default test timeout. For example: 15s, 2x, none',
+ defaultsTo: '30s');
+ parser.addFlag('ignore-timeouts',
+ help: 'Ignore all timeouts (useful if debugging)', negatable: false);
+ parser.addFlag('pause-after-load',
+ help: 'Pause for debugging before any tests execute.\n'
+ 'Implies --concurrency=1, --debug, and --ignore-timeouts.\n'
+ 'Currently only supported for browser tests.',
+ negatable: false);
+ parser.addFlag('debug',
+ help: 'Run the VM and Chrome tests in debug mode.', negatable: false);
+ parser.addOption('coverage',
+ help: 'Gather coverage and output it to the specified directory.\n'
+ 'Implies --debug.',
+ valueHelp: 'directory');
+ parser.addFlag('chain-stack-traces',
+ help: 'Use chained stack traces to provide greater exception details\n'
+ 'especially for asynchronous code. It may be useful to disable\n'
+ 'to provide improved test performance but at the cost of\n'
+ 'debuggability.',
+ defaultsTo: false);
+ parser.addFlag('no-retry',
+ help: "Don't rerun tests that have retry set.",
+ defaultsTo: false,
+ negatable: false);
+ parser.addFlag('use-data-isolate-strategy',
+ help: '**DEPRECATED**: This is now just an alias for --compiler source.',
+ defaultsTo: false,
+ hide: true,
+ negatable: false);
+ parser.addOption('test-randomize-ordering-seed',
+ help: 'Use the specified seed to randomize the execution order of test'
+ ' cases.\n'
+ 'Must be a 32bit unsigned integer or "random".\n'
+ 'If "random", pick a random seed to use.\n'
+ 'If not passed, do not randomize test case execution order.');
+ parser.addFlag('fail-fast',
+ help: 'Stop running tests after the first failure.\n');
+
+ var reporterDescriptions = <String, String>{
+ for (final MapEntry(:key, :value) in allReporters.entries)
+ key: value.description
+ };
+
+ parser.addSeparator('Output:');
+ parser.addOption('reporter',
+ abbr: 'r',
+ help: 'Set how to print test results.',
+ defaultsTo: defaultReporter,
+ allowed: allReporters.keys,
+ allowedHelp: reporterDescriptions,
+ valueHelp: 'option');
+ parser.addOption('file-reporter',
+ help: 'Enable an additional reporter writing test results to a file.\n'
+ 'Should be in the form <reporter>:<filepath>, '
+ 'Example: "json:reports/tests.json"');
+ parser.addFlag('verbose-trace',
+ negatable: false, help: 'Emit stack traces with core library frames.');
+ parser.addFlag('js-trace',
+ negatable: false,
+ help: 'Emit raw JavaScript stack traces for browser tests.');
+ parser.addFlag('color',
+ help: 'Use terminal colors.\n(auto-detected by default)');
+
+ /// The following options are used only by the internal Google test runner.
+ /// They're hidden and not supported as stable API surface outside Google.
+
+ parser.addOption('configuration',
+ help: 'The path to the configuration file.', hide: true);
+ parser.addMultiOption('dart2js-args',
+ help: 'Extra arguments to pass to dart2js.', hide: true);
+
+ // If we're running test/dir/my_test.dart, we'll look for
+ // test/dir/my_test.dart.html in the precompiled directory.
+ parser.addOption('precompiled',
+ help: 'The path to a mirror of the package directory containing HTML '
+ 'that points to precompiled JS.',
+ hide: true);
+
+ return parser;
+})();
+
+/// The usage string for the command-line arguments.
+String get usage => _parser.usage;
+
+/// Parses the configuration from [args].
+///
+/// Throws a [FormatException] if [args] are invalid.
+Configuration parse(List<String> args) => _Parser(args).parse();
+
+void _parseTestSelection(
+ String option, Map<String, Set<TestSelection>> selections) {
+ if (Platform.isWindows) {
+ // If given a path that starts with what looks like a drive letter, convert it
+ // into a file scheme URI. We can't parse using `Uri.file` because we do
+ // support query parameters which aren't valid file uris.
+ if (option.indexOf(':') == 1) {
+ option = 'file:///$option';
+ }
+ }
+ final uri = Uri.parse(option);
+ final path = Uri.decodeComponent(uri.path).stripDriveLetterLeadingSlash;
+ final names = uri.queryParametersAll['name'];
+ final fullName = uri.queryParameters['full-name'];
+ final line = uri.queryParameters['line'];
+ final col = uri.queryParameters['col'];
+
+ if (names != null && names.isNotEmpty && fullName != null) {
+ throw const FormatException(
+ 'Cannot specify both "name=<...>" and "full-name=<...>".',
+ );
+ }
+ final selection = TestSelection(
+ testPatterns: fullName != null
+ ? {RegExp('^${RegExp.escape(fullName)}\$')}
+ : {
+ if (names != null)
+ for (var name in names) RegExp(name)
+ },
+ line: line == null ? null : int.parse(line),
+ col: col == null ? null : int.parse(col),
+ );
+
+ selections.update(path, (selections) => selections..add(selection),
+ ifAbsent: () => {selection});
+}
+
+/// A class for parsing an argument list.
+///
+/// This is used to provide access to the arg results across helper methods.
+class _Parser {
+ /// The parsed options.
+ final ArgResults _options;
+
+ _Parser(List<String> args) : _options = _parser.parse(args);
+
+ List<String> _readMulti(String name) => _options[name] as List<String>;
+
+ /// Returns the parsed configuration.
+ Configuration parse() {
+ var patterns = [
+ for (var value in _readMulti('name'))
+ _wrapFormatException(value, () => RegExp(value), optionName: 'name'),
+ ..._readMulti('plain-name'),
+ ];
+
+ var includeTags = {..._readMulti('tags'), ..._readMulti('tag')}
+ .fold<BooleanSelector>(BooleanSelector.all, (selector, tag) {
+ return selector.intersection(BooleanSelector.parse(tag));
+ });
+
+ var excludeTags = {
+ ..._readMulti('exclude-tags'),
+ ..._readMulti('exclude-tag')
+ }.fold<BooleanSelector>(BooleanSelector.none, (selector, tag) {
+ return selector.union(BooleanSelector.parse(tag));
+ });
+
+ var shardIndex = _parseOption('shard-index', int.parse);
+ var totalShards = _parseOption('total-shards', int.parse);
+ if ((shardIndex == null) != (totalShards == null)) {
+ throw const FormatException(
+ '--shard-index and --total-shards may only be passed together.');
+ } else if (shardIndex != null) {
+ if (shardIndex < 0) {
+ throw const FormatException('--shard-index may not be negative.');
+ } else if (shardIndex >= totalShards!) {
+ throw const FormatException(
+ '--shard-index must be less than --total-shards.');
+ }
+ }
+
+ var reporter = _ifParsed('reporter') as String?;
+
+ var testRandomizeOrderingSeed =
+ _parseOption('test-randomize-ordering-seed', (value) {
+ var seed = value == 'random'
+ ? Random().nextInt(4294967295)
+ : int.parse(value).toUnsigned(32);
+
+ // TODO(#1547): Less hacky way of not breaking the json reporter
+ if (reporter != 'json') {
+ print('Shuffling test order with --test-randomize-ordering-seed=$seed');
+ }
+
+ return seed;
+ });
+
+ var color = _ifParsed<bool>('color') ?? canUseSpecialChars;
+
+ var runtimes =
+ _ifParsed<List<String>>('platform')?.map(RuntimeSelection.new).toList();
+ var compilerSelections = _ifParsed<List<String>>('compiler')
+ ?.map(CompilerSelection.parse)
+ .toList();
+ if (_ifParsed<bool>('use-data-isolate-strategy') == true) {
+ compilerSelections ??= [];
+ compilerSelections.add(CompilerSelection.parse('vm:source'));
+ }
+
+ final paths = _options.rest.isEmpty ? null : _options.rest;
+
+ Map<String, Set<TestSelection>>? selections;
+ if (paths != null) {
+ selections = {};
+ for (final path in paths) {
+ _parseTestSelection(path, selections);
+ }
+ }
+
+ if (_options.wasParsed('pub-serve')) {
+ throw ArgumentError(
+ 'The --pub-serve is no longer supported, if you require it please '
+ 'open an issue at https://github.com/dart-lang/test/issues/new.');
+ }
+
+ return Configuration(
+ help: _ifParsed('help'),
+ version: _ifParsed('version'),
+ verboseTrace: _ifParsed('verbose-trace'),
+ chainStackTraces: _ifParsed('chain-stack-traces'),
+ jsTrace: _ifParsed('js-trace'),
+ pauseAfterLoad: _ifParsed('pause-after-load'),
+ debug: _ifParsed('debug'),
+ color: color,
+ configurationPath: _ifParsed('configuration'),
+ dart2jsArgs: _ifParsed('dart2js-args'),
+ precompiledPath: _ifParsed<String>('precompiled'),
+ reporter: reporter,
+ fileReporters: _parseFileReporterOption(),
+ coverage: _ifParsed('coverage'),
+ concurrency: _parseOption('concurrency', int.parse),
+ shardIndex: shardIndex,
+ totalShards: totalShards,
+ timeout: _parseOption('timeout', Timeout.parse),
+ globalPatterns: patterns,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ runSkipped: _ifParsed('run-skipped'),
+ chosenPresets: _ifParsed('preset'),
+ testSelections: selections,
+ includeTags: includeTags,
+ excludeTags: excludeTags,
+ noRetry: _ifParsed('no-retry'),
+ testRandomizeOrderingSeed: testRandomizeOrderingSeed,
+ ignoreTimeouts: _ifParsed('ignore-timeouts'),
+ stopOnFirstFailure: _ifParsed('fail-fast'),
+ // Config that isn't supported on the command line
+ addTags: null,
+ allowTestRandomization: null,
+ allowDuplicateTestNames: null,
+ customHtmlTemplatePath: null,
+ defineRuntimes: null,
+ filename: null,
+ foldTraceExcept: null,
+ foldTraceOnly: null,
+ onPlatform: null,
+ overrideRuntimes: null,
+ presets: null,
+ retry: null,
+ skip: null,
+ skipReason: null,
+ testOn: null,
+ tags: null);
+ }
+
+ /// Returns the parsed option for [name], or `null` if none was parsed.
+ ///
+ /// If the user hasn't explicitly chosen a value, we want to pass null values
+ /// to [Configuration.new] so that it considers those fields unset when
+ /// merging with configuration from the config file.
+ T? _ifParsed<T>(String name) =>
+ _options.wasParsed(name) ? _options[name] as T : null;
+
+ /// Runs [parse] on the value of the option [name], and wraps any
+ /// [FormatException] it throws with additional information.
+ T? _parseOption<T>(String name, T Function(String) parse) {
+ if (!_options.wasParsed(name)) return null;
+
+ var value = _options[name];
+ if (value == null) return null;
+
+ return _wrapFormatException(value, () => parse(value as String),
+ optionName: name);
+ }
+
+ Map<String, String>? _parseFileReporterOption() =>
+ _parseOption('file-reporter', (value) {
+ if (!value.contains(':')) {
+ throw const FormatException(
+ 'option must be in the form <reporter>:<filepath>, e.g. '
+ '"json:reports/tests.json"');
+ }
+ final sep = value.indexOf(':');
+ final reporter = value.substring(0, sep);
+ if (!allReporters.containsKey(reporter)) {
+ throw FormatException('"$reporter" is not a supported reporter');
+ }
+ return {reporter: value.substring(sep + 1)};
+ });
+
+ /// Runs [parse], and wraps any [FormatException] it throws with additional
+ /// information.
+ T _wrapFormatException<T>(Object? value, T Function() parse,
+ {String? optionName}) {
+ try {
+ return parse();
+ } on FormatException catch (error) {
+ throw FormatException(
+ 'Couldn\'t parse ${optionName == null ? '' : '--$optionName '}"$value": '
+ '${error.message}');
+ }
+ }
+}
+
+extension _RuntimeDescription on Runtime {
+ String get supportedCompilersText {
+ var message = StringBuffer('[$identifier]: ');
+ message.write('${defaultCompiler.identifier} (default)');
+ for (var compiler in supportedCompilers) {
+ if (compiler == defaultCompiler) continue;
+ message.write(', ${compiler.identifier}');
+ }
+ return message.toString();
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration/custom_runtime.dart b/pkgs/test_core/lib/src/runner/configuration/custom_runtime.dart
new file mode 100644
index 0000000..d1bd028
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration/custom_runtime.dart
@@ -0,0 +1,34 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+import 'package:yaml/yaml.dart';
+
+/// A user-defined test runtime, based on an existing runtime but with
+/// different configuration.
+final class CustomRuntime {
+ /// The human-friendly name of the runtime.
+ final String name;
+
+ /// The location that [name] was defined in the configuration file.
+ final SourceSpan nameSpan;
+
+ /// The identifier used to look up the runtime.
+ final String identifier;
+
+ /// The location that [identifier] was defined in the configuration file.
+ final SourceSpan identifierSpan;
+
+ /// The identifier of the runtime that this extends.
+ final String parent;
+
+ /// The location that [parent] was defined in the configuration file.
+ final SourceSpan parentSpan;
+
+ /// The user's settings for this runtime.
+ final YamlMap settings;
+
+ CustomRuntime(this.name, this.nameSpan, this.identifier, this.identifierSpan,
+ this.parent, this.parentSpan, this.settings);
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration/load.dart b/pkgs/test_core/lib/src/runner/configuration/load.dart
new file mode 100644
index 0000000..9877db6
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration/load.dart
@@ -0,0 +1,656 @@
+// Copyright (c) 2016, 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:io';
+
+import 'package:boolean_selector/boolean_selector.dart';
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as p;
+import 'package:source_span/source_span.dart';
+import 'package:test_api/scaffolding.dart' show Timeout;
+import 'package:test_api/src/backend/operating_system.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/platform_selector.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/util/identifier_regex.dart'; // ignore: implementation_imports
+import 'package:yaml/yaml.dart';
+
+import '../../util/errors.dart';
+import '../../util/io.dart';
+import '../../util/pretty_print.dart';
+import '../compiler_selection.dart';
+import '../configuration.dart';
+import '../runtime_selection.dart';
+import '../suite.dart';
+import 'custom_runtime.dart';
+import 'reporters.dart';
+import 'runtime_settings.dart';
+
+/// A regular expression matching a Dart identifier.
+///
+/// This also matches a package name, since they must be Dart identifiers.
+final _identifierRegExp = RegExp(r'[a-zA-Z_]\w*');
+
+/// A regular expression matching allowed package names.
+///
+/// This allows dot-separated valid Dart identifiers. The dots are there for
+/// compatibility with Google's internal Dart packages, but they may not be used
+/// when publishing a package to pub.dev.
+final _packageName =
+ RegExp('^${_identifierRegExp.pattern}(\\.${_identifierRegExp.pattern})*\$');
+
+/// Parses configuration from YAML formatted [content].
+///
+/// If [global] is `true`, this restricts the configuration file to only rules
+/// that are supported globally.
+///
+/// If [sourceUrl] is provided then that will be set as the source url for
+/// the yaml document.
+///
+/// Throws a [FormatException] if the configuration is invalid.
+Configuration parse(String content, {Uri? sourceUrl, bool global = false}) {
+ var document = loadYamlNode(content, sourceUrl: sourceUrl);
+
+ if (document.value == null) return Configuration.empty;
+
+ if (document is! Map) {
+ throw SourceSpanFormatException(
+ 'The configuration must be a YAML map.', document.span, content);
+ }
+
+ var loader =
+ _ConfigurationLoader(document as YamlMap, content, global: global);
+ return loader.load();
+}
+
+/// A helper for [load] that tracks the YAML document.
+class _ConfigurationLoader {
+ /// The parsed configuration document.
+ final YamlMap _document;
+
+ /// The source string for [_document].
+ ///
+ /// Used for error reporting.
+ final String _source;
+
+ /// Whether this is parsing the global configuration file.
+ final bool _global;
+
+ /// Whether runner configuration is allowed at this level.
+ final bool _runnerConfig;
+
+ _ConfigurationLoader(this._document, this._source,
+ {bool global = false, bool runnerConfig = true})
+ : _global = global,
+ _runnerConfig = runnerConfig;
+
+ /// Loads the configuration in [_document].
+ Configuration load() => _loadIncludeConfig()
+ .merge(_loadGlobalTestConfig())
+ .merge(_loadLocalTestConfig())
+ .merge(_loadGlobalRunnerConfig())
+ .merge(_loadLocalRunnerConfig());
+
+ /// If an `include` node is contained in [node], merges and returns [config].
+ Configuration _loadIncludeConfig() {
+ if (!_runnerConfig) {
+ _disallow('include');
+ return Configuration.empty;
+ }
+
+ var includeNode = _document.nodes['include'];
+ if (includeNode == null) return Configuration.empty;
+
+ var includePath = _parseNode(includeNode, 'include path', p.fromUri);
+ var basePath =
+ p.join(p.dirname(p.fromUri(_document.span.sourceUrl)), includePath);
+ try {
+ return Configuration.load(basePath);
+ } on FileSystemException catch (error) {
+ throw SourceSpanFormatException(
+ getErrorMessage(error), includeNode.span, _source);
+ }
+ }
+
+ /// Loads test configuration that's allowed in the global configuration file.
+ Configuration _loadGlobalTestConfig() {
+ var verboseTrace = _getBool('verbose_trace');
+ var chainStackTraces = _getBool('chain_stack_traces');
+ var foldStackFrames = _loadFoldedStackFrames();
+ var jsTrace = _getBool('js_trace');
+
+ var timeout = _parseValue('timeout', Timeout.parse);
+
+ var onPlatform = _getMap('on_platform',
+ key: (keyNode) => _parseNode(keyNode, 'on_platform key',
+ (value) => PlatformSelector.parse(value, keyNode.span)),
+ value: (valueNode) =>
+ _nestedConfig(valueNode, 'on_platform value', runnerConfig: false));
+
+ var onOS = _getMap('on_os',
+ key: (keyNode) {
+ _validate(keyNode, 'on_os key must be a string.',
+ (value) => value is String);
+
+ var os = OperatingSystem.find(keyNode.value as String);
+ if (os != OperatingSystem.none) return os;
+
+ throw SourceSpanFormatException(
+ 'Invalid on_os key: No such operating system.',
+ keyNode.span,
+ _source);
+ },
+ value: (valueNode) => _nestedConfig(valueNode, 'on_os value'));
+
+ var presets = _getMap('presets',
+ key: (keyNode) => _parseIdentifierLike(keyNode, 'presets key'),
+ value: (valueNode) => _nestedConfig(valueNode, 'presets value'));
+
+ var config = Configuration.globalTest(
+ verboseTrace: verboseTrace,
+ jsTrace: jsTrace,
+ timeout: timeout,
+ presets: presets,
+ chainStackTraces: chainStackTraces,
+ foldTraceExcept: foldStackFrames['except'],
+ foldTraceOnly: foldStackFrames['only'])
+ .merge(_extractPresets<PlatformSelector>(
+ onPlatform, Configuration.onPlatform));
+
+ var osConfig = onOS[currentOS];
+ return osConfig == null ? config : config.merge(osConfig);
+ }
+
+ /// Loads test configuration that's not allowed in the global configuration
+ /// file.
+ ///
+ /// If [_global] is `true`, this will error if there are any local test-level
+ /// configuration fields.
+ Configuration _loadLocalTestConfig() {
+ if (_global) {
+ _disallow('skip');
+ _disallow('retry');
+ _disallow('test_on');
+ _disallow('add_tags');
+ _disallow('tags');
+ _disallow('allow_test_randomization');
+ _disallow('allow_duplicate_test_names');
+ return Configuration.empty;
+ }
+
+ var skipRaw = _getValue('skip', 'boolean or string',
+ (value) => (value is bool?) || value is String?);
+ String? skipReason;
+ bool? skip;
+ if (skipRaw is String) {
+ skipReason = skipRaw;
+ skip = true;
+ } else {
+ skip = skipRaw as bool?;
+ }
+
+ var testOn = _parsePlatformSelector('test_on');
+
+ var addTags = _getList(
+ 'add_tags', (tagNode) => _parseIdentifierLike(tagNode, 'Tag name'));
+
+ var tags = _getMap('tags',
+ key: (keyNode) =>
+ _parseNode(keyNode, 'tags key', BooleanSelector.parse),
+ value: (valueNode) =>
+ _nestedConfig(valueNode, 'tag value', runnerConfig: false));
+
+ var retry = _getNonNegativeInt('retry');
+
+ var allowTestRandomization = _getBool('allow_test_randomization');
+
+ var allowDuplicateTestNames = _getBool('allow_duplicate_test_names');
+
+ return Configuration.localTest(
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags,
+ allowTestRandomization: allowTestRandomization,
+ allowDuplicateTestNames: allowDuplicateTestNames)
+ .merge(_extractPresets<BooleanSelector>(tags, Configuration.tags));
+ }
+
+ /// Loads runner configuration that's allowed in the global configuration
+ /// file.
+ ///
+ /// If [_runnerConfig] is `false`, this will error if there are any
+ /// runner-level configuration fields.
+ Configuration _loadGlobalRunnerConfig() {
+ if (!_runnerConfig) {
+ _disallow('pause_after_load');
+ _disallow('reporter');
+ _disallow('file_reporters');
+ _disallow('concurrency');
+ _disallow('names');
+ _disallow('plain_names');
+ _disallow('platforms');
+ _disallow('add_presets');
+ _disallow('override_platforms');
+ _disallow('include');
+ return Configuration.empty;
+ }
+
+ var pauseAfterLoad = _getBool('pause_after_load');
+ var runSkipped = _getBool('run_skipped');
+
+ var reporter = _getString('reporter');
+ if (reporter != null && !allReporters.keys.contains(reporter)) {
+ _error('Unknown reporter "$reporter".', 'reporter');
+ }
+
+ var fileReporters = _getMap('file_reporters', key: (keyNode) {
+ _validate(keyNode, 'file_reporters key must be a string',
+ (value) => value is String);
+ final reporter = keyNode.value as String;
+ if (!allReporters.keys.contains(reporter)) {
+ _error('Unknown reporter "$reporter".', 'file_reporters');
+ }
+ return reporter;
+ }, value: (valueNode) {
+ _validate(valueNode, 'file_reporters value must be a string',
+ (value) => value is String);
+ return valueNode.value as String;
+ });
+
+ var concurrency = _getInt('concurrency');
+
+ // The UI term "platform" corresponds with the implementation term
+ // "runtime". The [Runtime] class used to be called [TestPlatform], but it
+ // was changed to avoid conflicting with [SuitePlatform]. We decided not to
+ // also change the UI to avoid a painful migration.
+ var runtimes = _getList(
+ 'platforms',
+ (runtimeNode) => RuntimeSelection(
+ _parseIdentifierLike(runtimeNode, 'Platform name'),
+ runtimeNode.span));
+
+ var compilerSelections = _getList(
+ 'compilers',
+ (node) => _parseNode(
+ node,
+ 'compiler',
+ (option) =>
+ CompilerSelection.parse(option, parentSpan: node.span)));
+
+ var chosenPresets = _getList('add_presets',
+ (presetNode) => _parseIdentifierLike(presetNode, 'Preset name'));
+
+ var overrideRuntimes = _loadOverrideRuntimes();
+
+ var customHtmlTemplatePath = _getString('custom_html_template_path');
+
+ return Configuration.globalRunner(
+ pauseAfterLoad: pauseAfterLoad,
+ customHtmlTemplatePath: customHtmlTemplatePath,
+ runSkipped: runSkipped,
+ reporter: reporter,
+ fileReporters: fileReporters,
+ concurrency: concurrency,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ chosenPresets: chosenPresets,
+ overrideRuntimes: overrideRuntimes);
+ }
+
+ /// Loads the `override_platforms` field.
+ Map<String, RuntimeSettings> _loadOverrideRuntimes() {
+ var runtimesNode =
+ _getNode('override_platforms', 'map', (value) => value is Map)
+ as YamlMap?;
+ if (runtimesNode == null) return const {};
+
+ var runtimes = <String, RuntimeSettings>{};
+ runtimesNode.nodes.forEach((identifierNode, valueNode) {
+ var yamlNode = identifierNode as YamlNode;
+ var identifier = _parseIdentifierLike(yamlNode, 'Platform identifier');
+
+ _validate(valueNode, 'Platform definition must be a map.',
+ (value) => value is Map);
+ var map = valueNode as YamlMap;
+
+ var settings = _expect(map, 'settings');
+ _validate(settings, 'Must be a map.', (value) => value is Map);
+
+ runtimes[identifier] =
+ RuntimeSettings(identifier, yamlNode.span, [settings as YamlMap]);
+ });
+ return runtimes;
+ }
+
+ /// Loads runner configuration that's not allowed in the global configuration
+ /// file.
+ ///
+ /// If [_runnerConfig] is `false` or if [_global] is `true`, this will error
+ /// if there are any local test-level configuration fields.
+ Configuration _loadLocalRunnerConfig() {
+ if (!_runnerConfig || _global) {
+ _disallow('names');
+ _disallow('plain_names');
+ _disallow('paths');
+ _disallow('filename');
+ _disallow('include_tags');
+ _disallow('exclude_tags');
+ _disallow('define_platforms');
+ return Configuration.empty;
+ }
+
+ var patterns = _getList('names', (nameNode) {
+ _validate(nameNode, 'Names must be strings.', (value) => value is String);
+ return _parseNode(nameNode, 'name', RegExp.new);
+ })
+ ..addAll(_getList('plain_names', (nameNode) {
+ _validate(
+ nameNode, 'Names must be strings.', (value) => value is String);
+ return _parseNode(nameNode, 'name', RegExp.new);
+ }));
+
+ var paths = _getList('paths', (pathNode) {
+ _validate(pathNode, 'Paths must be strings.', (value) => value is String);
+ _validate(pathNode, 'Paths must be relative.',
+ (value) => p.url.isRelative(value as String));
+ return _parseNode(pathNode, 'path', p.fromUri);
+ });
+
+ var filename = _parseValue('filename', Glob.new);
+
+ var includeTags = _parseBooleanSelector('include_tags');
+ var excludeTags = _parseBooleanSelector('exclude_tags');
+
+ var defineRuntimes = _loadDefineRuntimes();
+
+ return Configuration.localRunner(
+ globalPatterns: patterns,
+ testSelections: {
+ for (var path in paths) path: const {TestSelection()}
+ },
+ filename: filename,
+ includeTags: includeTags,
+ excludeTags: excludeTags,
+ defineRuntimes: defineRuntimes);
+ }
+
+ /// Returns a map representation of the `fold_stack_frames` configuration.
+ ///
+ /// The key `except` will correspond to the list of packages to fold.
+ /// The key `only` will correspond to the list of packages to keep in a
+ /// test [Chain].
+ Map<String, List<String>> _loadFoldedStackFrames() {
+ var foldOptionSet = false;
+ return _getMap('fold_stack_frames', key: (keyNode) {
+ _validate(keyNode, 'Must be a string', (value) => value is String);
+ _validate(keyNode, 'Must be "only" or "except".',
+ (value) => value == 'only' || value == 'except');
+
+ if (foldOptionSet) {
+ throw SourceSpanFormatException(
+ 'Can only contain one of "only" or "except".',
+ keyNode.span,
+ _source);
+ }
+ foldOptionSet = true;
+ return keyNode.value as String;
+ }, value: (valueNode) {
+ _validate(
+ valueNode,
+ 'Folded packages must be strings.',
+ (valueList) =>
+ valueList is YamlList &&
+ valueList.every((value) => value is String));
+
+ _validate(
+ valueNode,
+ 'Invalid package name.',
+ (valueList) => (valueList as Iterable)
+ .every((value) => _packageName.hasMatch(value as String)));
+
+ return List<String>.from(valueNode.value as Iterable);
+ });
+ }
+
+ /// Loads the `define_platforms` field.
+ Map<String, CustomRuntime> _loadDefineRuntimes() {
+ var runtimesNode =
+ _getNode('define_platforms', 'map', (value) => value is Map)
+ as YamlMap?;
+ if (runtimesNode == null) return const {};
+
+ var runtimes = <String, CustomRuntime>{};
+ runtimesNode.nodes.forEach((identifierNode, valueNode) {
+ var yamlNode = identifierNode as YamlNode;
+ var identifier = _parseIdentifierLike(yamlNode, 'Platform identifier');
+
+ _validate(valueNode, 'Platform definition must be a map.',
+ (value) => value is Map);
+ var map = valueNode as YamlMap;
+
+ var nameNode = _expect(map, 'name');
+ _validate(nameNode, 'Must be a string.', (value) => value is String);
+ var name = nameNode.value as String;
+
+ var parentNode = _expect(map, 'extends');
+ var parent = _parseIdentifierLike(parentNode, 'Platform parent');
+
+ var settings = _expect(map, 'settings');
+ _validate(settings, 'Must be a map.', (value) => value is Map);
+
+ runtimes[identifier] = CustomRuntime(name, nameNode.span, identifier,
+ yamlNode.span, parent, parentNode.span, settings as YamlMap);
+ });
+ return runtimes;
+ }
+
+ /// Throws an exception with [message] if [test] returns `false` when passed
+ /// [node]'s value.
+ void _validate(YamlNode node, String message, bool Function(dynamic) test) {
+ if (test(node.value)) return;
+ throw SourceSpanFormatException(message, node.span, _source);
+ }
+
+ /// Returns the value of the node at [field].
+ ///
+ /// If [typeTest] returns `false` for that value, instead throws an error
+ /// complaining that the field is not a [typeName].
+ Object? _getValue(
+ String field, String typeName, bool Function(dynamic) typeTest) {
+ var value = _document[field];
+ if (value == null || typeTest(value)) return value;
+ _error('$field must be ${a(typeName)}.', field);
+ }
+
+ /// Returns the YAML node at [field].
+ ///
+ /// If [typeTest] returns `false` for that node's value, instead throws an
+ /// error complaining that the field is not a [typeName].
+ ///
+ /// Returns `null` if [field] does not have a node in [_document].
+ YamlNode? _getNode(
+ String field, String typeName, bool Function(dynamic) typeTest) {
+ var node = _document.nodes[field];
+ if (node == null) return null;
+ _validate(node, '$field must be ${a(typeName)}.', typeTest);
+ return node;
+ }
+
+ /// Asserts that [field] is an int and returns its value.
+ int? _getInt(String field) =>
+ _getValue(field, 'int', (value) => value is int?) as int?;
+
+ /// Asserts that [field] is a non-negative int and returns its value.
+ int? _getNonNegativeInt(String field) =>
+ _getValue(field, 'non-negative int', (value) {
+ if (value == null) return true;
+ return value is int && value >= 0;
+ }) as int?;
+
+ /// Asserts that [field] is a boolean and returns its value.
+ bool? _getBool(String field) =>
+ _getValue(field, 'boolean', (value) => value is bool?) as bool?;
+
+ /// Asserts that [field] is a string and returns its value.
+ String? _getString(String field) =>
+ _getValue(field, 'string', (value) => value is String?) as String?;
+
+ /// Asserts that [field] is a list and runs [forElement] for each element it
+ /// contains.
+ ///
+ /// Returns a list of values returned by [forElement].
+ List<T> _getList<T>(String field, T Function(YamlNode) forElement) {
+ var node = _getNode(field, 'list', (value) => value is List) as YamlList?;
+ if (node == null) return [];
+ return node.nodes.map(forElement).toList();
+ }
+
+ /// Asserts that [field] is a map and runs [key] and [value] for each pair.
+ ///
+ /// Returns a map with the keys and values returned by [key] and [value]. Each
+ /// of these defaults to asserting that the value is a string.
+ Map<K, V> _getMap<K, V>(String field,
+ {K Function(YamlNode)? key, V Function(YamlNode)? value}) {
+ var node = _getNode(field, 'map', (value) => value is Map) as YamlMap?;
+ if (node == null) return {};
+
+ key ??= (keyNode) {
+ _validate(
+ keyNode, '$field keys must be strings.', (value) => value is String);
+
+ return keyNode.value as K;
+ };
+
+ value ??= (valueNode) {
+ _validate(valueNode, '$field values must be strings.',
+ (value) => value is String);
+
+ return valueNode.value as V;
+ };
+
+ return node.nodes.map((keyNode, valueNode) =>
+ MapEntry(key!(keyNode as YamlNode), value!(valueNode)));
+ }
+
+ /// Verifies that [node]'s value is an optionally hyphenated Dart identifier,
+ /// and returns it
+ String _parseIdentifierLike(YamlNode node, String name) {
+ _validate(node, '$name must be a string.', (value) => value is String);
+ _validate(node, '$name must be an (optionally hyphenated) Dart identifier.',
+ (value) => (value as String).contains(anchoredHyphenatedIdentifier));
+ return node.value as String;
+ }
+
+ /// Parses [node]'s value as a boolean selector.
+ BooleanSelector? _parseBooleanSelector(String name) =>
+ _parseValue(name, BooleanSelector.parse);
+
+ /// Parses [node]'s value as a platform selector.
+ PlatformSelector? _parsePlatformSelector(String field) {
+ var node = _document.nodes[field];
+ if (node == null) return null;
+ return _parseNode(
+ node, field, (value) => PlatformSelector.parse(value, node.span));
+ }
+
+ /// Asserts that [node] is a string, passes its value to [parse], and returns
+ /// the result.
+ ///
+ /// If [parse] throws a [FormatException], it's wrapped to include [node]'s
+ /// span.
+ T _parseNode<T>(YamlNode node, String name, T Function(String) parse) {
+ _validate(node, '$name must be a string.', (value) => value is String);
+
+ try {
+ return parse(node.value as String);
+ } on FormatException catch (error) {
+ throw SourceSpanFormatException(
+ 'Invalid $name: ${error.message}', node.span, _source);
+ }
+ }
+
+ /// Asserts that [field] is a string, passes it to [parse], and returns the
+ /// result.
+ ///
+ /// If [parse] throws a [FormatException], it's wrapped to include [field]'s
+ /// span.
+ T? _parseValue<T>(String field, T Function(String) parse) {
+ var node = _document.nodes[field];
+ if (node == null) return null;
+ return _parseNode(node, field, parse);
+ }
+
+ /// Parses a nested configuration document.
+ ///
+ /// [name] is the name of the field, which is used for error-handling.
+ /// [runnerConfig] controls whether runner configuration is allowed in the
+ /// nested configuration. It defaults to [_runnerConfig].
+ Configuration _nestedConfig(YamlNode? node, String name,
+ {bool? runnerConfig}) {
+ if (node == null || node.value == null) return Configuration.empty;
+
+ _validate(node, '$name must be a map.', (value) => value is Map);
+ var loader = _ConfigurationLoader(node as YamlMap, _source,
+ global: _global, runnerConfig: runnerConfig ?? _runnerConfig);
+ return loader.load();
+ }
+
+ /// Takes a map that contains [Configuration]s and extracts any
+ /// preset-specific configuration into a parent [Configuration].
+ ///
+ /// This is needed because parameters to [Configuration.new] such as
+ /// `onPlatform` take maps to [SuiteConfiguration]s. [SuiteConfiguration]
+ /// doesn't support preset-specific configuration, so this extracts the preset
+ /// logic into a parent [Configuration], leaving only maps to
+ /// [SuiteConfiguration]s. The [create] function is used to construct
+ /// [Configuration]s from the resolved maps.
+ Configuration _extractPresets<T>(Map<T, Configuration> map,
+ Configuration Function(Map<T, SuiteConfiguration>) create) {
+ if (map.isEmpty) return Configuration.empty;
+
+ var base = <T, SuiteConfiguration>{};
+ var presets = <String, Map<T, SuiteConfiguration>>{};
+ map.forEach((key, config) {
+ base[key] = config.suiteDefaults;
+ config.presets.forEach((preset, presetConfig) {
+ presets.putIfAbsent(preset, () => {})[key] = presetConfig.suiteDefaults;
+ });
+ });
+
+ if (presets.isEmpty) {
+ return base.isEmpty ? Configuration.empty : create(base);
+ } else {
+ var newPresets = presets.map((key, map) => MapEntry(key, create(map)));
+ return create(base).change(presets: newPresets);
+ }
+ }
+
+ /// Asserts that [map] has a field named [field] and returns it.
+ YamlNode _expect(YamlMap map, String field) {
+ var node = map.nodes[field];
+ if (node != null) return node;
+
+ throw SourceSpanFormatException(
+ 'Missing required field "$field".', map.span, _source);
+ }
+
+ /// Throws an error if a field named [field] exists at this level.
+ void _disallow(String field) {
+ if (!_document.containsKey(field)) return;
+
+ throw SourceSpanFormatException(
+ "$field isn't supported here.",
+ // We need the key as a [YamlNode] to get its span.
+ (_document.nodes.keys.firstWhere((key) => key.value == field)
+ as YamlNode)
+ .span,
+ _source);
+ }
+
+ /// Throws a [SourceSpanFormatException] with [message] about [field].
+ Never _error(String message, String field) {
+ throw SourceSpanFormatException(
+ message, _document.nodes[field]!.span, _source);
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration/reporters.dart b/pkgs/test_core/lib/src/runner/configuration/reporters.dart
new file mode 100644
index 0000000..0f0fe01
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration/reporters.dart
@@ -0,0 +1,91 @@
+// 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.
+
+import 'dart:collection';
+import 'dart:io';
+
+import '../../util/io.dart';
+import '../configuration.dart';
+import '../engine.dart';
+import '../reporter.dart';
+import '../reporter/compact.dart';
+import '../reporter/expanded.dart';
+import '../reporter/failures_only.dart';
+import '../reporter/github.dart';
+import '../reporter/json.dart';
+
+/// Constructs a reporter for the provided engine with the provided
+/// configuration.
+typedef ReporterFactory = Reporter Function(Configuration, Engine, StringSink);
+
+/// Container for a reporter description and corresponding factory.
+class ReporterDetails {
+ final String description;
+ final ReporterFactory factory;
+ ReporterDetails(this.description, this.factory);
+}
+
+/// All reporters and their corresponding details.
+final UnmodifiableMapView<String, ReporterDetails> allReporters =
+ UnmodifiableMapView<String, ReporterDetails>(_allReporters);
+
+final _allReporters = <String, ReporterDetails>{
+ 'expanded': ReporterDetails(
+ 'A separate line for each update.',
+ (config, engine, sink) => ExpandedReporter.watch(engine, sink,
+ color: config.color,
+ printPath: config.testSelections.length > 1 ||
+ Directory(config.testSelections.keys.single).existsSync(),
+ printPlatform: config.suiteDefaults.runtimes.length > 1 ||
+ config.suiteDefaults.compilerSelections != null)),
+ 'compact': ReporterDetails(
+ 'A single line, updated continuously.',
+ (config, engine, sink) => CompactReporter.watch(engine, sink,
+ color: config.color,
+ printPath: config.testSelections.length > 1 ||
+ Directory(config.testSelections.keys.single).existsSync(),
+ printPlatform: config.suiteDefaults.runtimes.length > 1 ||
+ config.suiteDefaults.compilerSelections != null)),
+ 'failures-only': ReporterDetails(
+ 'A separate line for failing tests with no output for passing tests',
+ (config, engine, sink) => FailuresOnlyReporter.watch(engine, sink,
+ color: config.color,
+ printPath: config.testSelections.length > 1 ||
+ Directory(config.testSelections.keys.single).existsSync(),
+ printPlatform: config.suiteDefaults.runtimes.length > 1 ||
+ config.suiteDefaults.compilerSelections != null)),
+ 'github': ReporterDetails(
+ 'A custom reporter for GitHub Actions '
+ '(the default reporter when running on GitHub Actions).',
+ (config, engine, sink) => GithubReporter.watch(engine, sink,
+ printPath: config.testSelections.length > 1 ||
+ Directory(config.testSelections.keys.single).existsSync(),
+ printPlatform: config.suiteDefaults.runtimes.length > 1 ||
+ config.suiteDefaults.compilerSelections != null)),
+ 'json': ReporterDetails(
+ 'A machine-readable format (see '
+ 'https://dart.dev/go/test-docs/json_reporter.md).',
+ (config, engine, sink) =>
+ JsonReporter.watch(engine, sink, isDebugRun: config.debug)),
+ 'silent': ReporterDetails(
+ 'A reporter with no output. '
+ 'May be useful when only the exit code is meaningful.',
+ (config, engine, sink) => SilentReporter()),
+};
+
+final defaultReporter = inTestTests
+ ? 'expanded'
+ : inGithubContext
+ ? 'github'
+ : canUseSpecialChars
+ ? 'compact'
+ : 'expanded';
+
+/// **Do not call this function without express permission from the test package
+/// authors**.
+///
+/// This globally registers a reporter.
+void registerReporter(String name, ReporterDetails reporterDetails) {
+ _allReporters[name] = reporterDetails;
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration/runtime_settings.dart b/pkgs/test_core/lib/src/runner/configuration/runtime_settings.dart
new file mode 100644
index 0000000..e366b4d
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration/runtime_settings.dart
@@ -0,0 +1,26 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+import 'package:yaml/yaml.dart';
+
+import '../plugin/customizable_platform.dart';
+
+/// User-defined settings for a built-in test runtime.
+final class RuntimeSettings {
+ /// The identifier used to look up the runtime being overridden.
+ final String identifier;
+
+ /// The location that [identifier] was defined in the configuration file.
+ final SourceSpan identifierSpan;
+
+ /// The user's settings for this runtime.
+ ///
+ /// This is a list of settings, from most global to most specific, that will
+ /// eventually be merged using [CustomizablePlatform.mergePlatformSettings].
+ final List<YamlMap> settings;
+
+ RuntimeSettings(this.identifier, this.identifierSpan, List<YamlMap> settings)
+ : settings = List.unmodifiable(settings);
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration/utils.dart b/pkgs/test_core/lib/src/runner/configuration/utils.dart
new file mode 100644
index 0000000..d47526c
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration/utils.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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 'package:collection/collection.dart';
+
+/// Like [mergeMaps], but assumes both maps are unmodifiable and so avoids
+/// creating a new map unnecessarily.
+///
+/// The return value *may or may not* be unmodifiable.
+Map<K, V> mergeUnmodifiableMaps<K, V>(Map<K, V> map1, Map<K, V> map2,
+ {V Function(V, V)? value}) {
+ if (map1.isEmpty) return map2;
+ if (map2.isEmpty) return map1;
+ return mergeMaps(map1, map2, value: value);
+}
diff --git a/pkgs/test_core/lib/src/runner/configuration/values.dart b/pkgs/test_core/lib/src/runner/configuration/values.dart
new file mode 100644
index 0000000..6e2f50a
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/configuration/values.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2016, 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:io';
+import 'dart:math' as math;
+
+import 'package:glob/glob.dart';
+
+/// The default number of test suites to run at once.
+///
+/// This defaults to half the available processors, since presumably some of
+/// them will be used for the OS and other processes.
+final defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2);
+
+/// The default filename pattern.
+///
+/// This is stored here so that we don't have to recompile it multiple times.
+final defaultFilename = Glob('*_test.dart');
diff --git a/pkgs/test_core/lib/src/runner/console.dart b/pkgs/test_core/lib/src/runner/console.dart
new file mode 100644
index 0000000..3a4d6fa
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/console.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2016, 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:io';
+import 'dart:math' as math;
+
+import 'package:async/async.dart';
+
+import '../util/io.dart';
+
+/// An interactive console for taking user commands.
+class Console {
+ /// The registered commands.
+ final _commands = <String, _Command>{};
+
+ /// The pending next line of standard input, if we're waiting on one.
+ CancelableOperation? _nextLine;
+
+ /// Whether the console is currently running.
+ bool _running = false;
+
+ /// The terminal escape for red text, or the empty string if this is Windows
+ /// or not outputting to a terminal.
+ final String _red;
+
+ /// The terminal escape for bold text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _bold;
+
+ /// The terminal escape for removing test coloring, or the empty string if
+ /// this is Windows or not outputting to a terminal.
+ final String _noColor;
+
+ /// Creates a new [Console].
+ ///
+ /// If [color] is true, this uses Unix terminal colors.
+ Console({bool color = true})
+ : _red = color ? '\u001b[31m' : '',
+ _bold = color ? '\u001b[1m' : '',
+ _noColor = color ? '\u001b[0m' : '' {
+ registerCommand('help', 'Displays this help information.', _displayHelp);
+ }
+
+ /// Registers a command to be run whenever the user types [name].
+ ///
+ /// The [description] should be a one-line description of the command to print
+ /// in the help output. The [body] callback will be called when the user types
+ /// the command.
+ void registerCommand(
+ String name, String description, FutureOr<void> Function() body) {
+ if (_commands.containsKey(name)) {
+ throw ArgumentError('The console already has a command named "$name".');
+ }
+
+ _commands[name] = (name: name, description: description, body: body);
+ }
+
+ /// Starts running the console.
+ ///
+ /// This prints the initial prompt and loops while waiting for user input.
+ void start() {
+ _running = true;
+ unawaited(() async {
+ while (_running) {
+ stdout.write('> ');
+ _nextLine = stdinLines.cancelable((queue) => queue.next);
+ var commandName = await _nextLine!.value;
+ _nextLine = null;
+
+ var command = _commands[commandName];
+ if (command == null) {
+ stderr.writeln(
+ '${_red}Unknown command $_bold$commandName$_noColor$_red.'
+ '$_noColor');
+ } else {
+ await command.body();
+ }
+ }
+ }());
+ }
+
+ /// Stops the console running.
+ void stop() {
+ _running = false;
+ if (_nextLine != null) {
+ stdout.writeln();
+ _nextLine!.cancel();
+ }
+ }
+
+ /// Displays the help info for the console commands.
+ void _displayHelp() {
+ var maxCommandLength =
+ _commands.values.map((command) => command.name.length).reduce(math.max);
+
+ for (var command in _commands.values) {
+ var name = command.name.padRight(maxCommandLength + 4);
+ print('$_bold$name$_noColor${command.description}');
+ }
+ }
+}
+
+typedef _Command = ({
+ String name,
+ String description,
+ FutureOr<void> Function() body,
+});
diff --git a/pkgs/test_core/lib/src/runner/coverage.dart b/pkgs/test_core/lib/src/runner/coverage.dart
new file mode 100644
index 0000000..841bdd7
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/coverage.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2019, 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:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+import 'live_suite_controller.dart';
+
+/// Collects coverage and outputs to the [coveragePath] path.
+Future<void> writeCoverage(
+ String coveragePath, LiveSuiteController controller) async {
+ var suite = controller.liveSuite.suite;
+ var coverage = await controller.liveSuite.suite.gatherCoverage();
+ final outfile = File(p.join(coveragePath,
+ '${suite.path}.${suite.platform.runtime.name.toLowerCase()}.json'))
+ ..createSync(recursive: true);
+ final out = outfile.openWrite();
+ out.write(json.encode(coverage));
+ await out.flush();
+ await out.close();
+}
diff --git a/pkgs/test_core/lib/src/runner/coverage_stub.dart b/pkgs/test_core/lib/src/runner/coverage_stub.dart
new file mode 100644
index 0000000..64f69c7
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/coverage_stub.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, 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 'live_suite_controller.dart';
+
+Future<void> writeCoverage(
+ String coveragePath, LiveSuiteController controller) =>
+ throw UnsupportedError(
+ 'Coverage is only supported through the test runner.');
diff --git a/pkgs/test_core/lib/src/runner/dart2js_compiler_pool.dart b/pkgs/test_core/lib/src/runner/dart2js_compiler_pool.dart
new file mode 100644
index 0000000..a13a65a
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/dart2js_compiler_pool.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2022, 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:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+import '../util/dart.dart';
+import '../util/io.dart';
+import '../util/package_config.dart';
+import 'compiler_pool.dart';
+import 'suite.dart';
+
+/// A regular expression matching the first status line printed by dart2js.
+final _dart2jsStatus =
+ RegExp(r'^Dart file \(.*\) compiled to JavaScript: .*\n?');
+
+/// A pool of `dart2js` instances.
+///
+/// This limits the number of compiler instances running concurrently.
+class Dart2JsCompilerPool extends CompilerPool {
+ /// Extra arguments to pass to dart2js.
+ final List<String> _extraArgs;
+
+ /// The currently-active dart2js processes.
+ final _processes = <Process>{};
+
+ /// Creates a compiler pool that multiple instances of `dart2js` at once.
+ Dart2JsCompilerPool([Iterable<String>? extraArgs])
+ : _extraArgs = extraArgs?.toList() ?? const [];
+
+ /// Compiles [code] to [path].
+ ///
+ /// This wraps the Dart code in the standard browser-testing wrapper.
+ ///
+ /// The returned [Future] will complete once the `dart2js` process completes
+ /// *and* all its output has been printed to the command line.
+ @override
+ Future compileInternal(
+ String code, String path, SuiteConfiguration suiteConfig) {
+ return withTempDir((dir) async {
+ var wrapperPath = p.join(dir, 'runInBrowser.dart');
+ File(wrapperPath).writeAsStringSync(code);
+
+ var args = [
+ 'compile',
+ 'js',
+ for (var experiment in enabledExperiments)
+ '--enable-experiment=$experiment',
+ '--enable-asserts',
+ wrapperPath,
+ '--out=$path',
+ '--packages=${await packageConfigUri}',
+ '--disable-program-split',
+ ..._extraArgs,
+ ...suiteConfig.dart2jsArgs
+ ];
+
+ if (config.color) args.add('--enable-diagnostic-colors');
+
+ var process = await Process.start(Platform.resolvedExecutable, args);
+ if (closed) {
+ process.kill();
+ return;
+ }
+
+ _processes.add(process);
+
+ /// Wait until the process is entirely done to print out any output.
+ /// This can produce a little extra time for users to wait with no
+ /// update, but it also avoids some really nasty-looking interleaved
+ /// output. Write both stdout and stderr to the same buffer in case
+ /// they're intended to be printed in order.
+ var buffer = StringBuffer();
+
+ await Future.wait([
+ process.stdout.transform(utf8.decoder).forEach(buffer.write),
+ process.stderr.transform(utf8.decoder).forEach(buffer.write),
+ ]);
+
+ var exitCode = await process.exitCode;
+ _processes.remove(process);
+ if (closed) return;
+
+ var output = buffer.toString().replaceFirst(_dart2jsStatus, '');
+ if (output.isNotEmpty) print(output);
+
+ if (exitCode != 0) throw 'dart2js failed.';
+
+ _fixSourceMap('$path.map');
+ });
+ }
+
+ /// Fix up the source map at [mapPath] so that it points to absolute file:
+ /// URIs that are resolvable by the browser.
+ void _fixSourceMap(String mapPath) {
+ var map = jsonDecode(File(mapPath).readAsStringSync()) as Map;
+ var root = map['sourceRoot'] as String;
+
+ final mapUri = p.toUri(mapPath);
+ map.cast<String, List<Object?>>().update(
+ 'sources',
+ (sources) => [
+ for (var source in sources)
+ switch (Uri.parse('$root$source')) {
+ Uri(hasScheme: true) && final uri => uri.toString(),
+ Uri(:final path) when path.endsWith('/runInBrowser.dart') => '',
+ final uri => mapUri.resolveUri(uri).toString(),
+ }
+ ],
+ );
+
+ File(mapPath).writeAsStringSync(jsonEncode(map));
+ }
+
+ /// Closes the compiler pool.
+ ///
+ /// This kills all currently-running compilers and ensures that no more will
+ /// be started. It returns a [Future] that completes once all the compilers
+ /// have been killed and all resources released.
+ @override
+ Future<void> closeInternal() async {
+ await Future.wait(_processes.map((process) async {
+ process.kill();
+ await process.exitCode;
+ }));
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/debugger.dart b/pkgs/test_core/lib/src/runner/debugger.dart
new file mode 100644
index 0000000..5e59d88
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/debugger.dart
@@ -0,0 +1,219 @@
+// Copyright (c) 2016, 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 'package:async/async.dart';
+
+import '../util/async.dart';
+import '../util/io.dart';
+import 'configuration.dart';
+import 'console.dart';
+import 'engine.dart';
+import 'load_suite.dart';
+import 'reporter.dart';
+import 'runner_suite.dart';
+
+/// Runs [loadSuite] in debugging mode.
+///
+/// Runs the suite's tests using [engine]. The [reporter] should already be
+/// watching [engine], and the [config] should contain the user configuration
+/// for the test runner.
+///
+/// Returns a [CancelableOperation] that will complete once the suite has
+/// finished running. If the operation is canceled, the debugger will clean up
+/// any resources it allocated.
+CancelableOperation debug(
+ Engine engine, Reporter reporter, LoadSuite loadSuite) {
+ _Debugger? debugger;
+ var canceled = false;
+ return CancelableOperation.fromFuture(() async {
+ engine.suiteSink.add(loadSuite.changeSuite((runnerSuite) {
+ engine.pause();
+ return runnerSuite;
+ }));
+
+ var suite = await loadSuite.suite;
+ if (canceled || suite == null) return;
+
+ await (debugger = _Debugger(engine, reporter, suite)).run();
+ }(), onCancel: () {
+ canceled = true;
+ // Make sure the load test finishes so the engine can close.
+ engine.resume();
+ debugger?.close();
+ });
+}
+
+// TODO(nweiz): Test using the console and restarting a test once sdk#25369 is
+// fixed and the VM service client is released
+/// A debugger for a single test suite.
+class _Debugger {
+ /// The test runner configuration.
+ final _config = Configuration.current;
+
+ /// The engine that will run the suite.
+ final Engine _engine;
+
+ /// The reporter that's reporting [_engine]'s progress.
+ final Reporter _reporter;
+
+ /// The suite to run.
+ final RunnerSuite _suite;
+
+ /// The console through which the user can control the debugger.
+ ///
+ /// This is only visible when the test environment is paused, so as not to
+ /// overlap with the reporter's reporting.
+ final Console _console;
+
+ /// A completer that's used to manually unpause the test if the debugger is
+ /// closed.
+ final _pauseCompleter = CancelableCompleter<void>();
+
+ /// The subscription to [_suite.onDebugging].
+ StreamSubscription<bool>? _onDebuggingSubscription;
+
+ /// The subscription to [_suite.environment.onRestart].
+ late final StreamSubscription _onRestartSubscription;
+
+ /// Whether [close] has been called.
+ bool _closed = false;
+
+ bool get _json => _config.reporter == 'json';
+
+ _Debugger(this._engine, this._reporter, this._suite)
+ : _console = Console(color: Configuration.current.color) {
+ _console.registerCommand('restart',
+ 'Restart the current test after it finishes running.', _restartTest);
+
+ _onRestartSubscription = _suite.environment.onRestart.listen((_) {
+ _restartTest();
+ });
+ }
+
+ /// Runs the debugger.
+ ///
+ /// This prints information about the suite's debugger, then once the user has
+ /// had a chance to set breakpoints, runs the suite's tests.
+ Future run() async {
+ try {
+ await _pause();
+ if (_closed) return;
+
+ _onDebuggingSubscription = _suite.onDebugging.listen((debugging) {
+ if (debugging) {
+ _onDebugging();
+ } else {
+ _onNotDebugging();
+ }
+ });
+
+ _engine.resume();
+ await _engine.onIdle.first;
+ } finally {
+ close();
+ }
+ }
+
+ /// Prints URLs for the [_suite]'s debugger and waits for the user to tell the
+ /// suite to run.
+ Future _pause() async {
+ if (!_suite.environment.supportsDebugging) return;
+
+ try {
+ if (!_json) {
+ _reporter.pause();
+
+ var bold = _config.color ? '\u001b[1m' : '';
+ var yellow = _config.color ? '\u001b[33m' : '';
+ var noColor = _config.color ? '\u001b[0m' : '';
+ print('');
+
+ var runtime = _suite.platform.runtime;
+ if (runtime.isDartVM) {
+ var url = _suite.environment.observatoryUrl;
+ if (url == null) {
+ print('${yellow}Observatory URL not found.$noColor');
+ } else {
+ print('Observatory URL: $bold$url$noColor');
+ }
+ }
+
+ if (runtime.isHeadless && !runtime.isDartVM) {
+ var url = _suite.environment.remoteDebuggerUrl;
+ if (url == null) {
+ print('${yellow}Remote debugger URL not found.$noColor');
+ } else {
+ print('Remote debugger URL: $bold$url$noColor');
+ }
+ }
+
+ var buffer = StringBuffer('${bold}The test runner is paused.$noColor ');
+ if (runtime.isDartVM) {
+ buffer.write('Open the Observatory ');
+ } else {
+ if (!runtime.isHeadless) {
+ buffer.write('Open the dev console in $runtime ');
+ } else {
+ buffer.write('Open the remote debugger ');
+ }
+ }
+
+ buffer.write("and set breakpoints. Once you're finished, return to "
+ 'this terminal and press Enter.');
+
+ print(wordWrap(buffer.toString()));
+ }
+
+ await inCompletionOrder([
+ _suite.environment.displayPause(),
+ stdinLines.cancelable((queue) => queue.next),
+ _pauseCompleter.operation
+ ]).first;
+ } finally {
+ if (!_json) _reporter.resume();
+ }
+ }
+
+ /// Handles the environment pausing to debug.
+ ///
+ /// This starts the interactive console.
+ void _onDebugging() {
+ if (!_json) _reporter.pause();
+
+ if (!_json) {
+ print('\nEntering debugging console. Type "help" for help.');
+ }
+ _console.start();
+ }
+
+ /// Handles the environment starting up again.
+ ///
+ /// This closes the interactive console.
+ void _onNotDebugging() {
+ if (!_json) _reporter.resume();
+ _console.stop();
+ }
+
+ /// Restarts the current test.
+ void _restartTest() {
+ if (_engine.active.isEmpty) return;
+ var liveTest = _engine.active.single;
+ _engine.restartTest(liveTest);
+ if (!_json) {
+ print(wordWrap(
+ 'Will restart "${liveTest.test.name}" once it finishes running.'));
+ }
+ }
+
+ /// Closes the debugger and releases its resources.
+ void close() {
+ _pauseCompleter.complete();
+ _closed = true;
+ _onDebuggingSubscription?.cancel();
+ _onRestartSubscription.cancel();
+ _console.stop();
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/engine.dart b/pkgs/test_core/lib/src/runner/engine.dart
new file mode 100644
index 0000000..6a63627
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/engine.dart
@@ -0,0 +1,553 @@
+// Copyright (c) 2015, 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:math';
+
+import 'package:async/async.dart' hide Result;
+import 'package:collection/collection.dart';
+import 'package:pool/pool.dart';
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/live_test_controller.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/message.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
+
+import 'coverage_stub.dart' if (dart.library.io) 'coverage.dart';
+import 'live_suite.dart';
+import 'live_suite_controller.dart';
+import 'load_suite.dart';
+import 'runner_suite.dart';
+import 'util/iterable_set.dart';
+
+/// An [Engine] manages a run that encompasses multiple test suites.
+///
+/// Test suites are provided by passing them into [suiteSink]. Once all suites
+/// have been provided, the user should close [suiteSink] to indicate this.
+/// [run] won't terminate until [suiteSink] is closed. Suites will be run in the
+/// order they're provided to [suiteSink]. Tests within those suites will
+/// likewise be run in the order they're declared.
+///
+/// The current status of every test is visible via [liveTests]. [onTestStarted]
+/// can also be used to be notified when a test is about to be run.
+///
+/// The engine has some special logic for [LoadSuite]s and the tests they
+/// contain, referred to as "load tests". Load tests exist to provide visibility
+/// into the process of loading test files. As long as that process is
+/// proceeding normally users usually don't care about it, so the engine does
+/// not include them in [liveTests] and other collections.
+/// If a load test fails, it will be added to [failed] and [liveTests].
+///
+/// The test suite loaded by a load suite will be automatically be run by the
+/// engine; it doesn't need to be added to [suiteSink] manually.
+///
+/// Load tests will always be emitted through [onTestStarted] so users can watch
+/// their event streams once they start running.
+class Engine {
+ /// Whether [run] has been called yet.
+ var _runCalled = false;
+
+ /// Whether [close] has been called.
+ var _closed = false;
+
+ /// Whether [close] was called before all the tests finished running.
+ ///
+ /// This is `null` if close hasn't been called and the tests are still
+ /// running, `true` if close was called before the tests finished running, and
+ /// `false` if the tests finished running before close was called.
+ bool? _closedBeforeDone;
+
+ /// The coverage output directory.
+ String? _coverage;
+
+ /// The seed used to generate randomness for test case shuffling.
+ ///
+ /// If null or zero no shuffling will occur.
+ /// The same seed will shuffle the tests in the same way every time.
+ int? testRandomizeOrderingSeed;
+
+ /// Whether to stop running tests after a failure.
+ bool _stopOnFirstFailure;
+
+ /// A pool that limits the number of test suites running concurrently.
+ final Pool _runPool;
+
+ /// A completer that will complete when this engine is unpaused.
+ ///
+ /// `null` if this engine is not paused.
+ Completer? _pauseCompleter;
+
+ /// A future that completes once this is unpaused.
+ ///
+ /// If this engine isn't paused, this future completes immediately.
+ Future get _onUnpaused =>
+ _pauseCompleter == null ? Future.value() : _pauseCompleter!.future;
+
+ /// Whether all tests passed or were skipped.
+ ///
+ /// This fires once all tests have completed and [suiteSink] has been closed.
+ /// This will be `null` if [close] was called before all the tests finished
+ /// running.
+ Future<bool?> get success async {
+ await Future.wait(<Future>[_group.future, _runPool.done], eagerError: true);
+ if (_closedBeforeDone!) return null;
+ return liveTests.every((liveTest) =>
+ liveTest.state.result.isPassing &&
+ liveTest.state.status == Status.complete);
+ }
+
+ /// A group of futures for each test suite.
+ final _group = FutureGroup<void>();
+
+ /// All of the engine's stream subscriptions.
+ final _subscriptions = <StreamSubscription>{};
+
+ /// A sink used to pass [RunnerSuite]s in to the engine to run.
+ ///
+ /// Suites may be added as quickly as they're available; the Engine will only
+ /// run as many as necessary at a time based on its concurrency settings.
+ ///
+ /// Suites added to the sink will be closed by the engine based on its
+ /// internal logic.
+ Sink<RunnerSuite> get suiteSink => DelegatingSink(_suiteController.sink);
+ final _suiteController = StreamController<RunnerSuite>();
+
+ /// All the [RunnerSuite]s added to [suiteSink] so far.
+ ///
+ /// Note that if a [LoadSuite] is added, this will only contain that suite,
+ /// not the suite it loads.
+ Set<RunnerSuite> get addedSuites => UnmodifiableSetView(_addedSuites);
+ final _addedSuites = <RunnerSuite>{};
+
+ /// A broadcast stream that emits each [RunnerSuite] as it's added to the
+ /// engine via [suiteSink].
+ ///
+ /// Note that if a [LoadSuite] is added, this will only return that suite, not
+ /// the suite it loads.
+ ///
+ /// This is guaranteed to fire after the suite is added to [addedSuites].
+ Stream<RunnerSuite> get onSuiteAdded => _onSuiteAddedController.stream;
+ final _onSuiteAddedController = StreamController<RunnerSuite>.broadcast();
+
+ /// A broadcast stream that emits each [LiveSuite] as it's loaded.
+ ///
+ /// Note that unlike [onSuiteAdded], for suites that are loaded using
+ /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually
+ /// be emitted by this stream.
+ Stream<LiveSuite> get onSuiteStarted => _onSuiteStartedController.stream;
+ final _onSuiteStartedController = StreamController<LiveSuite>.broadcast();
+
+ /// All the currently-known tests that have run or are running.
+ ///
+ /// These are [LiveTest]s, representing the in-progress state of each test.
+ /// Tests that have not yet begun running are marked [Status.pending]; tests
+ /// that have finished are marked [Status.complete].
+ ///
+ /// This is guaranteed to contain the same tests as the union of [passed],
+ /// [skipped], [failed], and [active].
+ ///
+ /// [LiveTest.run] must not be called on these tests.
+ Set<LiveTest> get liveTests =>
+ UnionSet.from([passed, skipped, failed, IterableSet(active)],
+ disjoint: true);
+
+ /// A stream that emits each [LiveTest] as it's about to start running.
+ ///
+ /// This is guaranteed to fire before [LiveTest.onStateChange] first fires.
+ Stream<LiveTest> get onTestStarted => _onTestStartedGroup.stream;
+ final _onTestStartedGroup = StreamGroup<LiveTest>.broadcast();
+
+ /// The set of tests that have completed and been marked as passing.
+ Set<LiveTest> get passed => _passedGroup.set;
+ final _passedGroup = UnionSetController<LiveTest>(disjoint: true);
+
+ /// The set of tests that have completed and been marked as skipped.
+ Set<LiveTest> get skipped => _skippedGroup.set;
+ final _skippedGroup = UnionSetController<LiveTest>(disjoint: true);
+
+ /// The set of tests that have completed and been marked as failing or error.
+ Set<LiveTest> get failed => _failedGroup.set;
+ final _failedGroup = UnionSetController<LiveTest>(disjoint: true);
+
+ /// The tests that are still running, in the order they began running.
+ List<LiveTest> get active => UnmodifiableListView(_active);
+ final _active = QueueList<LiveTest>();
+
+ /// The suites that are still loading, in the order they began.
+ List<LiveTest> get activeSuiteLoads =>
+ UnmodifiableListView(_activeSuiteLoads);
+ final _activeSuiteLoads = <LiveTest>{};
+
+ /// The set of tests that have been marked for restarting.
+ ///
+ /// This is always a subset of [active]. Once a test in here has finished
+ /// running, it's run again.
+ final _restarted = <LiveTest>{};
+
+ /// Whether this engine is idle—that is, not currently executing a test.
+ bool get isIdle => _group.isIdle;
+
+ /// A broadcast stream that fires an event whenever [isIdle] switches from
+ /// `false` to `true`.
+ Stream get onIdle => _group.onIdle;
+
+ /// Creates an [Engine] that will run all tests provided via [suiteSink].
+ ///
+ /// [concurrency] controls how many suites are loaded and ran at once, and
+ /// defaults to 1.
+ ///
+ /// [testRandomizeOrderingSeed] configures test case shuffling within each
+ /// test suite.
+ /// Any non-zero value will enable shuffling using this value as a seed.
+ /// Omitting this argument or passing `0` disables shuffling.
+ ///
+ /// [coverage] specifies a directory to output coverage information.
+ ///
+ /// If [stopOnFirstFailure] then a single failing test will cause the engine
+ /// to [close] and stop ruunning further tests.
+ Engine({
+ int? concurrency,
+ String? coverage,
+ this.testRandomizeOrderingSeed,
+ bool stopOnFirstFailure = false,
+ }) : _runPool = Pool(concurrency ?? 1),
+ _stopOnFirstFailure = stopOnFirstFailure,
+ _coverage = coverage {
+ _group.future.then((_) {
+ _onTestStartedGroup.close();
+ _onSuiteStartedController.close();
+ _closedBeforeDone ??= false;
+ }).onError((_, __) {
+ // Don't top-level errors. They'll be thrown via [success] anyway.
+ });
+ }
+
+ /// Creates an [Engine] that will run all tests in [suites].
+ ///
+ /// An engine constructed this way will automatically close its [suiteSink],
+ /// meaning that no further suites may be provided.
+ ///
+ /// [concurrency] controls how many suites are run at once. If [runSkipped] is
+ /// `true`, skipped tests will be run as though they weren't skipped.
+ factory Engine.withSuites(List<RunnerSuite> suites,
+ {int? concurrency, String? coverage, bool stopOnFirstFailure = false}) {
+ var engine = Engine(
+ concurrency: concurrency,
+ coverage: coverage,
+ stopOnFirstFailure: stopOnFirstFailure,
+ );
+ for (var suite in suites) {
+ engine.suiteSink.add(suite);
+ }
+ engine.suiteSink.close();
+ return engine;
+ }
+
+ /// Runs all tests in all suites defined by this engine.
+ ///
+ /// This returns `true` if all tests succeed, and `false` otherwise. It will
+ /// only return once all tests have finished running and [suiteSink] has been
+ /// closed.
+ ///
+ /// If [success] completes with `null` this will complete with `null`.
+ Future<bool?> run() {
+ if (_runCalled) {
+ throw StateError('Engine.run() may not be called more than once.');
+ }
+ _runCalled = true;
+
+ var subscription = _suiteController.stream.listen(null);
+ subscription
+ ..onData((suite) {
+ _addedSuites.add(suite);
+ _onSuiteAddedController.add(suite);
+
+ _group.add(() async {
+ var resource = await _runPool.request();
+ LiveSuiteController? controller;
+ try {
+ if (suite is LoadSuite) {
+ await _onUnpaused;
+ controller = await _addLoadSuite(suite);
+ if (controller == null) return;
+ } else {
+ controller = LiveSuiteController(suite);
+ }
+
+ _addLiveSuite(controller.liveSuite);
+
+ if (_closed) return;
+ await _runGroup(controller, controller.liveSuite.suite.group, []);
+ controller.noMoreLiveTests();
+ if (_coverage != null) await writeCoverage(_coverage!, controller);
+ } finally {
+ resource.allowRelease(() => controller?.close());
+ }
+ }());
+ })
+ ..onDone(() {
+ _subscriptions.remove(subscription);
+ _onSuiteAddedController.close();
+ _group.close();
+ _runPool.close();
+ });
+ _subscriptions.add(subscription);
+
+ return success;
+ }
+
+ /// Runs all the entries in [group] in sequence.
+ ///
+ /// [suiteController] is the controller fo the suite that contains [group].
+ /// [parents] is a list of groups that contain [group]. It may be modified,
+ /// but it's guaranteed to be in its original state once this function has
+ /// finished.
+ Future _runGroup(LiveSuiteController suiteController, Group group,
+ List<Group> parents) async {
+ parents.add(group);
+ try {
+ var suiteConfig = suiteController.liveSuite.suite.config;
+ var skipGroup = !suiteConfig.runSkipped && group.metadata.skip;
+ var setUpAllSucceeded = true;
+ if (!skipGroup && group.setUpAll != null) {
+ var liveTest = group.setUpAll!
+ .load(suiteController.liveSuite.suite, groups: parents);
+ await _runLiveTest(suiteController, liveTest, countSuccess: false);
+ setUpAllSucceeded = liveTest.state.result.isPassing;
+ }
+
+ if (!_closed && setUpAllSucceeded) {
+ // shuffle the group entries
+ var entries = group.entries.toList();
+ if (suiteConfig.allowTestRandomization &&
+ testRandomizeOrderingSeed != null &&
+ testRandomizeOrderingSeed! > 0) {
+ entries.shuffle(Random(testRandomizeOrderingSeed));
+ }
+
+ for (var entry in entries) {
+ if (_closed) return;
+
+ if (entry is Group) {
+ await _runGroup(suiteController, entry, parents);
+ } else if (!suiteConfig.runSkipped && entry.metadata.skip) {
+ await _runSkippedTest(suiteController, entry as Test, parents);
+ } else {
+ var test = entry as Test;
+ await _runLiveTest(suiteController,
+ test.load(suiteController.liveSuite.suite, groups: parents));
+ }
+ }
+ }
+
+ // Even if we're closed or setUpAll failed, we want to run all the
+ // teardowns to ensure that any state is properly cleaned up.
+ if (!skipGroup && group.tearDownAll != null) {
+ var liveTest = group.tearDownAll!
+ .load(suiteController.liveSuite.suite, groups: parents);
+ await _runLiveTest(suiteController, liveTest, countSuccess: false);
+ if (_closed) await liveTest.close();
+ }
+ } finally {
+ parents.remove(group);
+ }
+ }
+
+ /// Runs [liveTest] using [suiteController].
+ ///
+ /// If [countSuccess] is `true` (the default), the test is put into [passed]
+ /// if it succeeds. Otherwise, it's removed from [liveTests] entirely.
+ Future _runLiveTest(LiveSuiteController suiteController, LiveTest liveTest,
+ {bool countSuccess = true}) async {
+ await _onUnpaused;
+ _active.add(liveTest);
+
+ var subscription = liveTest.onStateChange.listen(null);
+ subscription
+ ..onData((state) {
+ if (state.status != Status.complete) return;
+ _active.remove(liveTest);
+ })
+ ..onDone(() {
+ _subscriptions.remove(subscription);
+ });
+ _subscriptions.add(subscription);
+
+ suiteController.reportLiveTest(liveTest, countSuccess: countSuccess);
+
+ // Schedule a microtask to ensure that [onTestStarted] fires before the
+ // first [LiveTest.onStateChange] event.
+ await Future.microtask(liveTest.run);
+
+ // Once the test finishes, use [new Future] to do a coarse-grained event
+ // loop pump to avoid starving non-microtask events.
+ await Future(() {});
+
+ if (!_restarted.contains(liveTest)) {
+ if (_stopOnFirstFailure && liveTest.state.result.isFailing) {
+ unawaited(close());
+ }
+ return;
+ }
+ await _runLiveTest(suiteController, liveTest.copy(),
+ countSuccess: countSuccess);
+ _restarted.remove(liveTest);
+ }
+
+ /// Runs a dummy [LiveTest] for a test marked as "skip".
+ ///
+ /// [suiteController] is the controller for the suite that contains [test].
+ /// [parents] is a list of groups that contain [test].
+ Future _runSkippedTest(LiveSuiteController suiteController, Test test,
+ List<Group> parents) async {
+ await _onUnpaused;
+ var skipped = LocalTest(test.name, test.metadata, () {}, trace: test.trace);
+
+ late LiveTestController controller;
+ controller =
+ LiveTestController(suiteController.liveSuite.suite, skipped, () {
+ controller.setState(const State(Status.running, Result.success));
+ controller.setState(const State(Status.running, Result.skipped));
+
+ if (skipped.metadata.skipReason != null) {
+ controller
+ .message(Message.skip('Skip: ${skipped.metadata.skipReason}'));
+ }
+
+ controller.setState(const State(Status.complete, Result.skipped));
+ controller.completer.complete();
+ }, () {}, groups: parents);
+
+ return await _runLiveTest(suiteController, controller);
+ }
+
+ /// Closes [liveTest] and tells the engine to re-run it once it's done
+ /// running.
+ ///
+ /// Returns the same future as [LiveTest.close].
+ Future restartTest(LiveTest liveTest) async {
+ if (_activeSuiteLoads.contains(liveTest)) {
+ throw ArgumentError("Can't restart a load test.");
+ }
+
+ if (!_active.contains(liveTest)) {
+ throw StateError("Can't restart inactive test "
+ '"${liveTest.test.name}".');
+ }
+
+ _restarted.add(liveTest);
+ _active.remove(liveTest);
+ await liveTest.close();
+ }
+
+ /// Runs [suite] and returns the [LiveSuiteController] for the suite it loads.
+ ///
+ /// Returns `null` if the suite fails to load.
+ Future<LiveSuiteController?> _addLoadSuite(LoadSuite suite) async {
+ var controller = LiveSuiteController(suite);
+ _addLiveSuite(controller.liveSuite);
+
+ var liveTest = suite.test.load(suite);
+ _activeSuiteLoads.add(liveTest);
+
+ var subscription = liveTest.onStateChange.listen(null);
+ subscription
+ ..onData((state) {
+ if (state.status != Status.complete) return;
+ _activeSuiteLoads.remove(liveTest);
+ })
+ ..onDone(() {
+ _subscriptions.remove(subscription);
+ });
+ _subscriptions.add(subscription);
+
+ controller.reportLiveTest(liveTest, countSuccess: false);
+ controller.noMoreLiveTests();
+
+ // Schedule a microtask to ensure that [onTestStarted] fires before the
+ // first [LiveTest.onStateChange] event.
+ await Future.microtask(liveTest.run);
+
+ var innerSuite = await suite.suite;
+ if (innerSuite == null) return null;
+
+ var innerController = LiveSuiteController(innerSuite);
+ unawaited(innerController.liveSuite.onClose.whenComplete(() {
+ // When the main suite is closed, close the load suite and its test as
+ // well. This doesn't release any resources, but it does close streams
+ // which indicates that the load test won't experience an error in the
+ // future.
+ liveTest.close();
+ controller.close();
+ }));
+
+ return innerController;
+ }
+
+ /// Add [liveSuite] and the information it exposes to the engine's
+ /// informational streams and collections.
+ void _addLiveSuite(LiveSuite liveSuite) {
+ _onSuiteStartedController.add(liveSuite);
+
+ _onTestStartedGroup.add(liveSuite.onTestStarted);
+ _passedGroup.add(liveSuite.passed);
+ _skippedGroup.add(liveSuite.skipped);
+ _failedGroup.add(liveSuite.failed);
+ }
+
+ /// Pauses the engine.
+ ///
+ /// This pauses all streams and keeps any new suites from being loaded or
+ /// tests from being run until [resume] is called.
+ ///
+ /// This does nothing if the engine is already paused. Pauses are *not*
+ /// cumulative.
+ void pause() {
+ if (_pauseCompleter != null) return;
+ _pauseCompleter = Completer();
+ for (var subscription in _subscriptions) {
+ subscription.pause();
+ }
+ }
+
+ void resume() {
+ if (_pauseCompleter == null) return;
+ _pauseCompleter!.complete();
+ _pauseCompleter = null;
+ for (var subscription in _subscriptions) {
+ subscription.resume();
+ }
+ }
+
+ /// Signals that the caller is done paying attention to test results and the
+ /// engine should release any resources it has allocated.
+ ///
+ /// Any actively-running tests are also closed. VM tests are allowed to finish
+ /// running so that any modifications they've made to the filesystem can be
+ /// cleaned up.
+ ///
+ /// **Note that closing the engine is not the same as closing [suiteSink].**
+ /// Closing [suiteSink] indicates that no more input will be provided, closing
+ /// the engine indicates that no more output should be emitted.
+ Future close() async {
+ _closed = true;
+ if (_closedBeforeDone != null) _closedBeforeDone = true;
+ await _suiteController.close();
+ await _onSuiteAddedController.close();
+
+ // Close the running tests first so that we're sure to wait for them to
+ // finish before we close their suites and cause them to become unloaded.
+ var allLiveTests = liveTests.toSet()..addAll(_activeSuiteLoads);
+ var futures = allLiveTests.map((liveTest) => liveTest.close()).toList();
+
+ // Closing the run pool will close the test suites as soon as their tests
+ // are done. For browser suites this is effectively immediate since their
+ // tests shut down as soon as they're closed, but for VM suites we may need
+ // to wait for tearDowns or tearDownAlls to run.
+ futures.add(_runPool.close());
+ await Future.wait(futures, eagerError: true);
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/environment.dart b/pkgs/test_core/lib/src/runner/environment.dart
new file mode 100644
index 0000000..7fcba66
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/environment.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2015, 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 'package:async/async.dart';
+
+/// The abstract class of environments in which test suites are
+/// loaded—specifically, browsers and the Dart VM.
+abstract class Environment {
+ /// Whether this environment supports interactive debugging.
+ bool get supportsDebugging;
+
+ /// The URL of the Dart VM Observatory for this environment, or `null` if this
+ /// environment doesn't run the Dart VM or the URL couldn't be detected.
+ Uri? get observatoryUrl;
+
+ /// The URL of the remote debugger for this environment, or `null` if it isn't
+ /// enabled.
+ Uri? get remoteDebuggerUrl;
+
+ /// A broadcast stream that emits a `null` event whenever the user tells the
+ /// environment to restart the current test once it's finished.
+ ///
+ /// Never emits an error, and never closes.
+ Stream get onRestart;
+
+ /// Displays information indicating that the test runner is paused.
+ ///
+ /// The returned operation will complete when the user takes action within the
+ /// environment that should unpause the runner. If the runner is unpaused
+ /// elsewhere, the operation should be canceled.
+ CancelableOperation displayPause();
+}
+
+/// The default environment for platform plugins.
+class PluginEnvironment implements Environment {
+ @override
+ final supportsDebugging = false;
+ @override
+ Stream get onRestart => StreamController<void>.broadcast().stream;
+
+ const PluginEnvironment();
+
+ @override
+ Uri? get observatoryUrl => null;
+
+ @override
+ Uri? get remoteDebuggerUrl => null;
+
+ @override
+ CancelableOperation displayPause() => throw UnsupportedError(
+ 'PluginEnvironment.displayPause is not supported.');
+}
diff --git a/pkgs/test_core/lib/src/runner/hack_register_platform.dart b/pkgs/test_core/lib/src/runner/hack_register_platform.dart
new file mode 100644
index 0000000..f5a618d
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/hack_register_platform.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2016, 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:collection';
+
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'platform.dart';
+
+/// The functions to use to load [_platformPlugins] in all loaders.
+///
+/// **Do not access this outside the test package**.
+final platformCallbacks =
+ UnmodifiableMapView<Runtime, FutureOr<PlatformPlugin> Function()>(
+ _platformCallbacks);
+final _platformCallbacks = <Runtime, FutureOr<PlatformPlugin> Function()>{};
+
+/// **Do not call this function without express permission from the test package
+/// authors**.
+///
+/// Registers a [PlatformPlugin] for [runtimes].
+///
+/// This globally registers a plugin for all [Loader]s. When the runner first
+/// requests that a suite be loaded for one of the given runtimes, this will
+/// call [plugin] to load the platform plugin. It may return either a
+/// [PlatformPlugin] or a `Future<PlatformPlugin>`. That plugin is then
+/// preserved and used to load all suites for all matching runtimes.
+///
+/// This overwrites the default plugins for those runtimes.
+void registerPlatformPlugin(
+ Iterable<Runtime> runtimes, FutureOr<PlatformPlugin> Function() plugin) {
+ for (var runtime in runtimes) {
+ _platformCallbacks[runtime] = plugin;
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/hybrid_listener.dart b/pkgs/test_core/lib/src/runner/hybrid_listener.dart
new file mode 100644
index 0000000..6a521db
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/hybrid_listener.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2016, 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:isolate';
+
+import 'package:async/async.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:stream_channel/isolate_channel.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart' show RemoteException;
+import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
+
+/// A sink transformer that wraps data and error events so that errors can be
+/// decoded after being JSON-serialized.
+final _transformer = StreamSinkTransformer<dynamic, dynamic>.fromHandlers(
+ handleData: (data, sink) {
+ ensureJsonEncodable(data);
+ sink.add({'type': 'data', 'data': data});
+}, handleError: (error, stackTrace, sink) {
+ sink.add(
+ {'type': 'error', 'error': RemoteException.serialize(error, stackTrace)});
+});
+
+/// Runs the body of a hybrid isolate and communicates its messages, errors, and
+/// prints to the main isolate.
+///
+/// The [getMain] function returns the `hybridMain()` method. It's wrapped in a
+/// closure so that, if the method undefined, we can catch the error and notify
+/// the caller of it.
+///
+/// The [data] argument contains two values: a [SendPort] that communicates with
+/// the main isolate, and a message to pass to `hybridMain()`.
+void listen(Function Function() getMain, List data) {
+ var channel = IsolateChannel<Object?>.connectSend(data.first as SendPort);
+ var message = data.last;
+
+ Chain.capture(() {
+ runZoned(() {
+ dynamic /*Function*/ main;
+ try {
+ main = getMain();
+ } on NoSuchMethodError catch (_) {
+ _sendError(channel, 'No top-level hybridMain() function defined.');
+ return;
+ } catch (error, stackTrace) {
+ _sendError(channel, error, stackTrace);
+ return;
+ }
+
+ if (main is! Function) {
+ _sendError(channel, 'Top-level hybridMain is not a function.');
+ return;
+ } else if (main is! void Function(StreamChannel) &&
+ main is! void Function(StreamChannel, Never)) {
+ if (main is void Function(StreamChannel<Never>) ||
+ main is void Function(StreamChannel<Never>, Never)) {
+ _sendError(
+ channel,
+ 'The first parameter to the top-level hybridMain() must be a '
+ 'StreamChannel<dynamic> or StreamChannel<Object?>. More specific '
+ 'types such as StreamChannel<Object> are not supported.');
+ } else {
+ _sendError(channel,
+ 'Top-level hybridMain() function must take one or two arguments.');
+ }
+ return;
+ }
+
+ // Wrap [channel] before passing it to user code so that we can wrap
+ // errors and distinguish user data events from control events sent by the
+ // listener.
+ var transformedChannel = channel.transformSink(_transformer);
+ if (main is void Function(StreamChannel)) {
+ main(transformedChannel);
+ } else {
+ main(transformedChannel, message);
+ }
+ }, zoneSpecification: ZoneSpecification(print: (_, __, ___, line) {
+ channel.sink.add({'type': 'print', 'line': line});
+ }));
+ }, onError: (error, stackTrace) async {
+ _sendError(channel, error, stackTrace);
+ await channel.sink.close();
+ Isolate.current.kill();
+ });
+}
+
+/// Sends a message over [channel] indicating an error from user code.
+void _sendError(StreamChannel channel, Object error, [StackTrace? stackTrace]) {
+ channel.sink.add({
+ 'type': 'error',
+ 'error': RemoteException.serialize(error, stackTrace ?? Chain.current())
+ });
+}
diff --git a/pkgs/test_core/lib/src/runner/live_suite.dart b/pkgs/test_core/lib/src/runner/live_suite.dart
new file mode 100644
index 0000000..771655d
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/live_suite.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2016, 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 'package:collection/collection.dart';
+
+import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+
+import 'runner_suite.dart';
+
+/// A view of the execution of a test suite.
+///
+/// This is distinct from [Suite] because it represents the progress of running
+/// a suite rather than the suite's contents. It provides events and collections
+/// that give the caller a view into the suite's current state.
+abstract class LiveSuite {
+ /// The suite that's being run.
+ RunnerSuite get suite;
+
+ /// A [Future] that completes once the suite is complete.
+ ///
+ /// Note that even once this completes, the suite may still be running code
+ /// asynchronously. A suite is considered complete once all of its tests are
+ /// complete, but it's possible for a test to continue running even after it's
+ /// been marked complete—see [LiveTest.isComplete] for details.
+ ///
+ /// The [onClose] future can be used to determine when the suite and its tests
+ /// are guaranteed to emit no more events.
+ Future get onComplete;
+
+ /// Whether the suite has been closed.
+ ///
+ /// If this is `true`, no code is running for the suite or any of its tests.
+ /// At this point, the caller can be sure that the suites' tests are all in
+ /// fixed states that will not change in the future.
+ bool get isClosed;
+
+ /// A [Future] that completes when the suite has been closed.
+ ///
+ /// Once this completes, no code is running for the suite or any of its tests.
+ /// At this point, the caller can be sure that the suites' tests are all in
+ /// fixed states that will not change in the future.
+ Future get onClose;
+
+ /// All the currently-known tests in this suite that have run or are running.
+ ///
+ /// This is guaranteed to contain the same tests as the union of [passed],
+ /// [skipped], [failed], and [active].
+ Set<LiveTest> get liveTests => UnionSet.from([
+ passed,
+ skipped,
+ failed,
+ if (active != null) {active!}
+ ]);
+
+ /// A stream that emits each [LiveTest] in this suite as it's about to start
+ /// running.
+ ///
+ /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. It
+ /// will close once all tests the user has selected are run.
+ Stream<LiveTest> get onTestStarted;
+
+ /// The set of tests in this suite that have completed and been marked as
+ /// passing.
+ Set<LiveTest> get passed;
+
+ /// The set of tests in this suite that have completed and been marked as
+ /// skipped.
+ Set<LiveTest> get skipped;
+
+ /// The set of tests in this suite that have completed and been marked as
+ /// failing or error.
+ Set<LiveTest> get failed;
+
+ /// The currently running test in this suite, or `null` if no test is running.
+ LiveTest? get active;
+}
diff --git a/pkgs/test_core/lib/src/runner/live_suite_controller.dart b/pkgs/test_core/lib/src/runner/live_suite_controller.dart
new file mode 100644
index 0000000..15bc797
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/live_suite_controller.dart
@@ -0,0 +1,154 @@
+// Copyright (c) 2016, 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 'package:async/async.dart' hide Result;
+import 'package:collection/collection.dart';
+import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
+
+import 'live_suite.dart';
+import 'runner_suite.dart';
+
+/// An implementation of [LiveSuite] that's controlled by a
+/// [LiveSuiteController].
+class _LiveSuite extends LiveSuite {
+ final LiveSuiteController _controller;
+
+ @override
+ RunnerSuite get suite => _controller._suite;
+
+ @override
+ Future get onComplete => _controller._onCompleteGroup.future;
+
+ @override
+ bool get isClosed => _controller._onCloseCompleter.isCompleted;
+
+ @override
+ Future get onClose => _controller._onCloseCompleter.future;
+
+ @override
+ Stream<LiveTest> get onTestStarted =>
+ _controller._onTestStartedController.stream;
+
+ @override
+ Set<LiveTest> get passed => UnmodifiableSetView(_controller._passed);
+
+ @override
+ Set<LiveTest> get skipped => UnmodifiableSetView(_controller._skipped);
+
+ @override
+ Set<LiveTest> get failed => UnmodifiableSetView(_controller._failed);
+
+ @override
+ LiveTest? get active => _controller._active;
+
+ _LiveSuite(this._controller);
+}
+
+/// A controller that drives a [LiveSuite].
+///
+/// This is a utility class to make it easier for [Engine] to create the
+/// [LiveSuite]s exposed by various APIs. The [LiveSuite] is accessible through
+/// [LiveSuiteController.liveSuite]. When a live test is run, it should be
+/// passed to [reportLiveTest], and once tests are finished being run for this
+/// suite, [noMoreLiveTests] should be called. Once the suite should be torn
+/// down, [close] should be called.
+class LiveSuiteController {
+ /// The [LiveSuite] being controlled.
+ late final liveSuite = _LiveSuite(this);
+
+ /// The suite that's being run.
+ final RunnerSuite _suite;
+
+ /// The future group that backs [LiveSuite.onComplete].
+ ///
+ /// This contains all the futures from tests that are run in this suite.
+ final _onCompleteGroup = FutureGroup<void>();
+
+ /// The completer that backs [LiveSuite.onClose].
+ ///
+ /// This is completed when the live suite is closed.
+ final _onCloseCompleter = Completer<void>();
+
+ /// The controller for [LiveSuite.onTestStarted].
+ final _onTestStartedController =
+ StreamController<LiveTest>.broadcast(sync: true);
+
+ /// The set that backs [LiveTest.passed].
+ final _passed = <LiveTest>{};
+
+ /// The set that backs [LiveTest.skipped].
+ final _skipped = <LiveTest>{};
+
+ /// The set that backs [LiveTest.failed].
+ final _failed = <LiveTest>{};
+
+ /// The test exposed through [LiveTest.active].
+ LiveTest? _active;
+
+ /// Creates a controller for a live suite representing running the tests in
+ /// [suite].
+ ///
+ /// Once this is called, the controller assumes responsibility for closing the
+ /// suite. The caller should call [LiveSuiteController.close] rather than
+ /// calling [RunnerSuite.close] directly.
+ LiveSuiteController(this._suite);
+
+ /// Reports the status of [liveTest] through [liveSuite].
+ ///
+ /// The live test is assumed to be a member of this suite. If [countSuccess]
+ /// is `true` (the default), the test is put into [passed] if it succeeds.
+ /// Otherwise, it's removed from [liveTests] entirely.
+ ///
+ /// Throws a [StateError] if called after [noMoreLiveTests].
+ void reportLiveTest(LiveTest liveTest, {bool countSuccess = true}) {
+ if (_onTestStartedController.isClosed) {
+ throw StateError("Can't call reportLiveTest() after noMoreTests().");
+ }
+
+ assert(liveTest.suite == _suite);
+ assert(_active == null);
+
+ _active = liveTest;
+
+ liveTest.onStateChange.listen((state) {
+ if (state.status != Status.complete) return;
+ _active = null;
+
+ if (state.result == Result.skipped) {
+ _skipped.add(liveTest);
+ } else if (state.result != Result.success) {
+ _passed.remove(liveTest);
+ _failed.add(liveTest);
+ } else if (countSuccess) {
+ _passed.add(liveTest);
+ // A passing test that was once failing was retried
+ _failed.remove(liveTest);
+ }
+ });
+
+ _onTestStartedController.add(liveTest);
+
+ _onCompleteGroup.add(liveTest.onComplete);
+ }
+
+ /// Indicates that all the live tests that are going to be provided for this
+ /// suite have already been provided.
+ void noMoreLiveTests() {
+ _onTestStartedController.close();
+ _onCompleteGroup.close();
+ }
+
+ /// Closes the underlying suite.
+ Future close() => _closeMemo.runOnce(() async {
+ try {
+ await _suite.close();
+ } finally {
+ _onCloseCompleter.complete();
+ }
+ });
+ final _closeMemo = AsyncMemoizer<void>();
+}
diff --git a/pkgs/test_core/lib/src/runner/load_exception.dart b/pkgs/test_core/lib/src/runner/load_exception.dart
new file mode 100644
index 0000000..3fbd462
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/load_exception.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2015, 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 'package:source_span/source_span.dart';
+
+import '../util/errors.dart';
+
+class LoadException implements Exception {
+ final String path;
+
+ final Object innerError;
+
+ LoadException(this.path, this.innerError);
+
+ @override
+ String toString({bool color = false}) {
+ var buffer = StringBuffer();
+ if (color) buffer.write('\u001b[31m'); // red
+ buffer.write('Failed to load "$path":');
+ if (color) buffer.write('\u001b[0m'); // no color
+
+ var innerString = getErrorMessage(innerError);
+ if (innerError is SourceSpanException) {
+ innerString = (innerError as SourceSpanException)
+ .toString(color: color)
+ .replaceFirst(' of $path', '');
+ }
+
+ buffer.write(innerString.contains('\n') ? '\n' : ' ');
+ buffer.write(innerString);
+ return buffer.toString();
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/load_suite.dart b/pkgs/test_core/lib/src/runner/load_suite.dart
new file mode 100644
index 0000000..633739a
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/load_suite.dart
@@ -0,0 +1,220 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+import 'package:test_api/scaffolding.dart' show Timeout;
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
+
+import '../util/io_stub.dart' if (dart.library.io) '../util/io.dart';
+import 'load_exception.dart';
+import 'plugin/environment.dart';
+import 'runner_suite.dart';
+import 'suite.dart';
+
+/// The timeout for loading a test suite.
+///
+/// We want this to be long enough that even a very large application being
+/// compiled with dart2js doesn't trigger it, but short enough that it fires
+/// before the host kills it. For example, Google's Forge service has a
+/// 15-minute timeout.
+final _timeout = const Duration(minutes: 12);
+
+/// A [Suite] emitted by a [Loader] that provides a test-like interface for
+/// loading a test file.
+///
+/// This is used to expose the current status of test loading to the user. It's
+/// important to provide users visibility into what's taking a long time and
+/// where failures occur. And since some tests may be loaded at the same time as
+/// others are run, it's useful to provide that visibility in the form of a test
+/// suite so that it can integrate well into the existing reporting interface
+/// without too much extra logic.
+///
+/// A suite is constructed with logic necessary to produce a test suite. As with
+/// a normal test body, this logic isn't run until [LiveTest.run] is called. The
+/// suite itself is returned by [suite] once it's available, but any errors or
+/// prints will be emitted through the running [LiveTest].
+class LoadSuite extends Suite implements RunnerSuite {
+ @override
+ final environment = const PluginEnvironment();
+ @override
+ final SuiteConfiguration config;
+ @override
+ final isDebugging = false;
+ @override
+ final onDebugging = StreamController<bool>().stream;
+
+ @override
+ bool get isLoadSuite => true;
+
+ /// A future that completes to the loaded suite once the suite's test has been
+ /// run and completed successfully.
+ ///
+ /// This will return `null` if the suite is unavailable for some reason (for
+ /// example if an error occurred while loading it).
+ Future<RunnerSuite?> get suite async => (await _suiteAndZone)?.suite;
+
+ /// A future that completes to a pair of [suite] and the load test's [Zone].
+ ///
+ /// This will return `null` if the suite is unavailable for some reason (for
+ /// example if an error occurred while loading it).
+ final Future<({RunnerSuite suite, Zone zone})?> _suiteAndZone;
+
+ /// Returns the test that loads the suite.
+ ///
+ /// Load suites are guaranteed to only contain one test. This is a utility
+ /// method for accessing it directly.
+ Test get test => group.entries.single as Test;
+
+ /// Creates a load suite named [name] on [platform].
+ ///
+ /// [body] may return either a [RunnerSuite] or a [Future] that completes to a
+ /// [RunnerSuite]. Its return value is forwarded through [suite], although if
+ /// it throws an error that will be forwarded through the suite's test.
+ ///
+ /// If the the load test is closed before [body] is complete, it will close
+ /// the suite returned by [body] once it completes.
+ factory LoadSuite(String name, SuiteConfiguration config,
+ SuitePlatform platform, FutureOr<RunnerSuite?> Function() body,
+ {String? path}) {
+ var completer = Completer<({RunnerSuite suite, Zone zone})?>.sync();
+ return LoadSuite._(name, config, platform, () {
+ var invoker = Invoker.current;
+ invoker!.addOutstandingCallback();
+
+ unawaited(() async {
+ RunnerSuite? suite;
+ try {
+ suite = await body();
+ } catch (_) {
+ invoker.removeOutstandingCallback();
+ rethrow;
+ }
+ if (completer.isCompleted) {
+ // If the load test has already been closed, close the suite it
+ // generated.
+ await suite?.close();
+ return;
+ }
+
+ completer.complete(
+ suite == null ? null : (suite: suite, zone: Zone.current));
+ invoker.removeOutstandingCallback();
+ }());
+
+ // If the test completes before the body callback, either an out-of-band
+ // error occurred or the test was canceled. Either way, we return a `null`
+ // suite.
+ invoker.liveTest.onComplete.then((_) {
+ if (!completer.isCompleted) completer.complete();
+ });
+
+ // If the test is forcibly closed, let it complete, since load tests don't
+ // have timeouts.
+ invoker.onClose.then((_) => invoker.removeOutstandingCallback());
+ }, completer.future, path: path, ignoreTimeouts: config.ignoreTimeouts);
+ }
+
+ /// A utility constructor for a load suite that just throws [exception].
+ ///
+ /// The suite's name will be based on [exception]'s path.
+ factory LoadSuite.forLoadException(
+ LoadException exception, SuiteConfiguration? config,
+ {SuitePlatform? platform, StackTrace? stackTrace}) {
+ stackTrace ??= Trace.current();
+
+ return LoadSuite(
+ 'loading ${exception.path}',
+ config ?? SuiteConfiguration.empty,
+ platform ?? currentPlatform(Runtime.vm, null),
+ () => Future.error(exception, stackTrace),
+ path: exception.path);
+ }
+
+ /// A utility constructor for a load suite that just emits [suite].
+ factory LoadSuite.forSuite(RunnerSuite suite) {
+ return LoadSuite(
+ 'loading ${suite.path}', suite.config, suite.platform, () => suite,
+ path: suite.path);
+ }
+
+ LoadSuite._(String name, this.config, SuitePlatform platform,
+ void Function() body, this._suiteAndZone,
+ {required bool ignoreTimeouts, String? path})
+ : super(
+ Group.root(
+ [LocalTest(name, Metadata(timeout: Timeout(_timeout)), body)]),
+ platform,
+ path: path,
+ ignoreTimeouts: ignoreTimeouts);
+
+ /// A constructor used by [changeSuite].
+ LoadSuite._changeSuite(LoadSuite old, this._suiteAndZone)
+ : config = old.config,
+ super(old.group, old.platform,
+ path: old.path, ignoreTimeouts: old.ignoreTimeouts);
+
+ /// A constructor used by [filter].
+ LoadSuite._filtered(LoadSuite old, Group filtered)
+ : config = old.config,
+ _suiteAndZone = old._suiteAndZone,
+ super(old.group, old.platform,
+ path: old.path, ignoreTimeouts: old.ignoreTimeouts);
+
+ /// Creates a new [LoadSuite] that's identical to this one, but that
+ /// transforms [suite] once it's loaded.
+ ///
+ /// If [suite] completes to `null`, [change] won't be run. [change] is run
+ /// within the load test's zone, so any errors or prints it emits will be
+ /// associated with that test.
+ LoadSuite changeSuite(RunnerSuite? Function(RunnerSuite) change) {
+ return LoadSuite._changeSuite(this, _suiteAndZone.then((pair) {
+ if (pair == null) return null;
+
+ var (:suite, :zone) = pair;
+ RunnerSuite? newSuite;
+ zone.runGuarded(() {
+ newSuite = change(suite);
+ });
+ return newSuite == null ? null : (suite: newSuite!, zone: zone);
+ }));
+ }
+
+ /// Runs the test and returns the suite.
+ ///
+ /// Rather than emitting errors through a [LiveTest], this just pipes them
+ /// through the return value.
+ Future<RunnerSuite?> getSuite() async {
+ var liveTest = test.load(this);
+ liveTest.onMessage.listen((message) => print(message.text));
+ await liveTest.run();
+
+ if (liveTest.errors.isEmpty) return await suite;
+
+ var error = liveTest.errors.first;
+ await Future<void>.error(error.error, error.stackTrace);
+ throw 'unreachable';
+ }
+
+ @override
+ LoadSuite filter(bool Function(Test) callback) {
+ var filtered = group.filter(callback);
+ filtered ??= Group.root([], metadata: metadata);
+ return LoadSuite._filtered(this, filtered);
+ }
+
+ @override
+ Future close() async {}
+
+ @override
+ Future<Map<String, dynamic>> gatherCoverage() =>
+ throw UnsupportedError('Coverage is not supported for LoadSuite tests.');
+}
diff --git a/pkgs/test_core/lib/src/runner/loader.dart b/pkgs/test_core/lib/src/runner/loader.dart
new file mode 100644
index 0000000..c37105d
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/loader.dart
@@ -0,0 +1,297 @@
+// Copyright (c) 2015, 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:io';
+
+import 'package:async/async.dart';
+import 'package:path/path.dart' as p;
+import 'package:source_span/source_span.dart';
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:yaml/yaml.dart';
+
+import '../util/io.dart';
+import 'compiler_selection.dart';
+import 'configuration.dart';
+import 'hack_register_platform.dart';
+import 'load_exception.dart';
+import 'load_suite.dart';
+import 'parse_metadata.dart';
+import 'platform.dart';
+import 'plugin/customizable_platform.dart';
+import 'plugin/environment.dart';
+import 'runner_suite.dart';
+import 'suite.dart';
+import 'vm/platform.dart';
+
+/// A class for finding test files and loading them into a runnable form.
+class Loader {
+ /// The test runner configuration.
+ final _config = Configuration.current;
+
+ /// All suites that have been created by the loader.
+ final _suites = <RunnerSuite>{};
+
+ /// Memoizers for platform plugins, indexed by the runtimes they support.
+ final _platformPlugins = <Runtime, AsyncMemoizer<PlatformPlugin>>{};
+
+ /// The functions to use to load [_platformPlugins].
+ ///
+ /// These are passed to the plugins' async memoizers when a plugin is needed.
+ final _platformCallbacks = <Runtime, FutureOr<PlatformPlugin> Function()>{};
+
+ /// A map of all runtimes registered in [_platformCallbacks], indexed by
+ /// their string identifiers.
+ final _runtimesByIdentifier = <String, Runtime>{};
+
+ /// The user-provided settings for runtimes, as a list of settings that will
+ /// be merged together using [CustomizablePlatform.mergePlatformSettings].
+ final _runtimeSettings = <Runtime, List<YamlMap>>{};
+
+ /// The user-provided settings for runtimes.
+ final _parsedRuntimeSettings = <Runtime, Object>{};
+
+ /// All plaforms supported by this [Loader].
+ List<Runtime> get allRuntimes => List.unmodifiable(_platformCallbacks.keys);
+
+ /// The runtime variables supported by this loader, in addition the default
+ /// variables that are always supported.
+ Iterable<String> get _runtimeVariables =>
+ _platformCallbacks.keys.map((runtime) => runtime.identifier);
+
+ /// Creates a new loader that loads tests on platforms defined in
+ /// [Configuration.current].
+ Loader() {
+ _registerPlatformPlugin([Runtime.vm], VMPlatform.new);
+
+ platformCallbacks.forEach((runtime, plugin) {
+ _registerPlatformPlugin([runtime], plugin);
+ });
+
+ _registerCustomRuntimes();
+
+ _config.validateRuntimes(allRuntimes);
+
+ _registerRuntimeOverrides();
+ }
+
+ /// Registers a [PlatformPlugin] for [runtimes].
+ void _registerPlatformPlugin(
+ Iterable<Runtime> runtimes, FutureOr<PlatformPlugin> Function() plugin) {
+ var memoizer = AsyncMemoizer<PlatformPlugin>();
+ for (var runtime in runtimes) {
+ _platformPlugins[runtime] = memoizer;
+ _platformCallbacks[runtime] = plugin;
+ _runtimesByIdentifier[runtime.identifier] = runtime;
+ }
+ }
+
+ /// Registers user-defined runtimes from [Configuration.defineRuntimes].
+ void _registerCustomRuntimes() {
+ for (var customRuntime in _config.defineRuntimes.values) {
+ if (_runtimesByIdentifier.containsKey(customRuntime.identifier)) {
+ throw SourceSpanFormatException(
+ wordWrap(
+ 'The platform "${customRuntime.identifier}" already exists. '
+ 'Use override_platforms to override it.'),
+ customRuntime.identifierSpan);
+ }
+
+ var parent = _runtimesByIdentifier[customRuntime.parent];
+ if (parent == null) {
+ throw SourceSpanFormatException(
+ 'Unknown platform.', customRuntime.parentSpan);
+ }
+
+ var runtime = parent.extend(customRuntime.name, customRuntime.identifier);
+ _platformPlugins[runtime] = _platformPlugins[parent]!;
+ _platformCallbacks[runtime] = _platformCallbacks[parent]!;
+ _runtimesByIdentifier[runtime.identifier] = runtime;
+
+ _runtimeSettings[runtime] = [customRuntime.settings];
+ }
+ }
+
+ /// Registers users' runtime settings from [Configuration.overrideRuntimes].
+ void _registerRuntimeOverrides() {
+ for (var settings in _config.overrideRuntimes.values) {
+ var runtime = _runtimesByIdentifier[settings.identifier];
+ _runtimeSettings
+ .putIfAbsent(runtime!, () => [])
+ .addAll(settings.settings);
+ }
+ }
+
+ /// Returns the [Runtime] registered with this loader that's identified
+ /// by [identifier], or `null` if none can be found.
+ Runtime? findRuntime(String identifier) => _runtimesByIdentifier[identifier];
+
+ /// Loads all test suites in [dir] according to [suiteConfig].
+ ///
+ /// This will load tests from files that match the global configuration's
+ /// filename glob. Any tests that fail to load will be emitted as
+ /// [LoadException]s.
+ ///
+ /// This emits [LoadSuite]s that must then be run to emit the actual
+ /// [RunnerSuite]s defined in the file.
+ Stream<LoadSuite> loadDir(String dir, SuiteConfiguration suiteConfig) {
+ return StreamGroup.merge(
+ Directory(dir).listSync(recursive: true).map((entry) {
+ if (entry is! File || !_config.filename.matches(p.basename(entry.path))) {
+ return const Stream.empty();
+ }
+
+ return loadFile(entry.path, suiteConfig);
+ }));
+ }
+
+ /// Loads a test suite from the file at [path] according to [suiteConfig].
+ ///
+ /// This emits [LoadSuite]s that must then be run to emit the actual
+ /// [RunnerSuite]s defined in the file.
+ ///
+ /// This will emit a [LoadException] if the file fails to load.
+ Stream<LoadSuite> loadFile(
+ String path, SuiteConfiguration suiteConfig) async* {
+ try {
+ suiteConfig = suiteConfig.merge(SuiteConfiguration.fromMetadata(
+ parseMetadata(
+ path, File(path).readAsStringSync(), _runtimeVariables.toSet())));
+ } on ArgumentError catch (_) {
+ // Ignore the analyzer's error, since its formatting is much worse than
+ // the VM's or dart2js's.
+ } on FormatException catch (error, stackTrace) {
+ yield LoadSuite.forLoadException(LoadException(path, error), suiteConfig,
+ stackTrace: stackTrace);
+ return;
+ }
+
+ if (_config.excludeTags.evaluate(suiteConfig.metadata.tags.contains)) {
+ return;
+ }
+
+ for (var runtimeName in suiteConfig.runtimes) {
+ var runtime = findRuntime(runtimeName);
+ if (runtime == null) {
+ throw ArgumentError.value(runtimeName, 'platform', 'Unknown platform');
+ }
+ final compilers = {
+ for (var selection
+ in suiteConfig.compilerSelections ?? <CompilerSelection>[])
+ if (runtime.supportedCompilers.contains(selection.compiler) &&
+ (selection.platformSelector == null ||
+ selection.platformSelector!
+ .evaluate(currentPlatform(runtime, selection.compiler))))
+ selection.compiler,
+ };
+ if (compilers.isEmpty) compilers.add(runtime.defaultCompiler);
+
+ for (var compiler in compilers) {
+ var platform = currentPlatform(runtime, compiler);
+ if (!suiteConfig.metadata.testOn.evaluate(platform)) continue;
+
+ var platformConfig = suiteConfig.forPlatform(platform);
+
+ // Don't load a skipped suite.
+ if (platformConfig.metadata.skip && !platformConfig.runSkipped) {
+ yield LoadSuite.forSuite(RunnerSuite(
+ const PluginEnvironment(),
+ platformConfig,
+ Group.root([LocalTest('(suite)', platformConfig.metadata, () {})],
+ metadata: platformConfig.metadata),
+ platform,
+ path: path));
+ continue;
+ }
+
+ yield LoadSuite('loading $path', platformConfig, platform, () async {
+ var memo = _platformPlugins[platform.runtime]!;
+
+ var retriesLeft = suiteConfig.metadata.retry;
+ while (true) {
+ try {
+ var plugin =
+ await memo.runOnce(_platformCallbacks[platform.runtime]!);
+ _customizePlatform(plugin, platform.runtime);
+ var suite = await plugin.load(path, platform, platformConfig,
+ {'platformVariables': _runtimeVariables.toList()});
+ if (suite != null) _suites.add(suite);
+ return suite;
+ } on Object catch (error, stackTrace) {
+ if (retriesLeft > 0) {
+ retriesLeft--;
+ print('Retrying load of $path in 1s ($retriesLeft remaining)');
+ await Future<void>.delayed(const Duration(seconds: 1));
+ continue;
+ }
+ if (error is LoadException) {
+ rethrow;
+ }
+ await Future<void>.error(LoadException(path, error), stackTrace);
+ return null;
+ }
+ }
+ }, path: path);
+ }
+ }
+ }
+
+ /// Passes user-defined settings to [plugin] if necessary.
+ void _customizePlatform(PlatformPlugin plugin, Runtime runtime) {
+ var parsed = _parsedRuntimeSettings[runtime];
+ if (parsed != null) {
+ (plugin as CustomizablePlatform).customizePlatform(runtime, parsed);
+ return;
+ }
+
+ var settings = _runtimeSettings[runtime];
+ if (settings == null) return;
+
+ if (plugin is CustomizablePlatform) {
+ parsed = settings
+ .map(plugin.parsePlatformSettings)
+ .reduce(plugin.mergePlatformSettings);
+ plugin.customizePlatform(runtime, parsed);
+ _parsedRuntimeSettings[runtime] = parsed;
+ } else {
+ String identifier;
+ SourceSpan span;
+ if (runtime.isChild) {
+ identifier = runtime.parent!.identifier;
+ span = _config.defineRuntimes[runtime.identifier]!.parentSpan;
+ } else {
+ identifier = runtime.identifier;
+ span = _config.overrideRuntimes[runtime.identifier]!.identifierSpan;
+ }
+
+ throw SourceSpanFormatException(
+ 'The "$identifier" platform can\'t be customized.', span);
+ }
+ }
+
+ Future closeEphemeral() async {
+ await Future.wait(_platformPlugins.values.map((memo) async {
+ if (!memo.hasRun) return;
+ await (await memo.future).closeEphemeral();
+ }));
+ }
+
+ /// Closes the loader and releases all resources allocated by it.
+ Future close() => _closeMemo.runOnce(() async {
+ await Future.wait([
+ Future.wait(_platformPlugins.values.map((memo) async {
+ if (!memo.hasRun) return;
+ await (await memo.future).close();
+ })),
+ Future.wait(_suites.map((suite) => suite.close()))
+ ]);
+
+ _platformPlugins.clear();
+ _platformCallbacks.clear();
+ _suites.clear();
+ });
+ final _closeMemo = AsyncMemoizer<void>();
+}
diff --git a/pkgs/test_core/lib/src/runner/no_tests_found_exception.dart b/pkgs/test_core/lib/src/runner/no_tests_found_exception.dart
new file mode 100644
index 0000000..0f65837
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/no_tests_found_exception.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, 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.
+
+/// An expected exception thrown when there were no tests found to run.
+class NoTestsFoundException implements Exception {
+ final String message;
+
+ NoTestsFoundException(this.message);
+
+ @override
+ String toString() => message;
+}
diff --git a/pkgs/test_core/lib/src/runner/package_version.dart b/pkgs/test_core/lib/src/runner/package_version.dart
new file mode 100644
index 0000000..0ce77d3
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/package_version.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, 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 'package:package_config/package_config.dart';
+import 'package:path/path.dart' as p;
+
+import '../util/detaching_future.dart';
+import '../util/package_config.dart';
+
+/// A comment which forces the language version to be that of the current
+/// packages default.
+///
+/// If the cwd is not a package, this returns an empty string which ends up
+/// defaulting to the current sdk version.
+Future<String> get rootPackageLanguageVersionComment =>
+ _rootPackageLanguageVersionComment.asFuture;
+final _rootPackageLanguageVersionComment = DetachingFuture(() async {
+ var packageConfig = await loadPackageConfigUri(await packageConfigUri);
+ var rootPackage = packageConfig.packageOf(Uri.file(p.absolute('foo.dart')));
+ if (rootPackage == null) return '// <unknown-language-version>';
+ return '// @dart=${rootPackage.languageVersion}';
+}());
diff --git a/pkgs/test_core/lib/src/runner/parse_metadata.dart b/pkgs/test_core/lib/src/runner/parse_metadata.dart
new file mode 100644
index 0000000..fd2b5ee
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/parse_metadata.dart
@@ -0,0 +1,514 @@
+// Copyright (c) 2015, 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 'package:analyzer/dart/analysis/utilities.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:path/path.dart' as p;
+import 'package:source_span/source_span.dart';
+import 'package:test_api/scaffolding.dart' show Timeout;
+import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/platform_selector.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/util/identifier_regex.dart'; // ignore: implementation_imports
+
+import '../util/dart.dart';
+
+/// Parse the test metadata for the test file at [path] with [contents].
+///
+/// The [platformVariables] are the set of variables that are valid for platform
+/// selectors in suite metadata, in addition to the built-in variables that are
+/// allowed everywhere.
+///
+/// Throws an [AnalysisError] if parsing fails or a [FormatException] if the
+/// test annotations are incorrect.
+Metadata parseMetadata(
+ String path, String contents, Set<String> platformVariables) =>
+ _Parser(path, contents, platformVariables).parse();
+
+/// A parser for test suite metadata.
+class _Parser {
+ /// The path to the test suite.
+ final String _path;
+
+ /// The set of variables that are valid for platform selectors, in addition to
+ /// the built-in variables that are allowed everywhere.
+ final Set<String> _platformVariables;
+
+ /// All annotations at the top of the file.
+ late final List<Annotation> _annotations;
+
+ /// All prefixes defined by imports in this file.
+ late final Set<String> _prefixes;
+
+ /// The actual contents of the file.
+ final String _contents;
+
+ /// The language version override comment if one was present, otherwise null.
+ String? _languageVersionComment;
+
+ _Parser(this._path, this._contents, this._platformVariables) {
+ var result =
+ parseString(content: _contents, path: _path, throwIfDiagnostics: false);
+ var directives = result.unit.directives;
+ _annotations = directives.isEmpty ? [] : directives.first.metadata;
+ _languageVersionComment = result.unit.languageVersionToken?.value();
+
+ // We explicitly *don't* just look for "package:test" imports here,
+ // because it could be re-exported from another library.
+ _prefixes = directives
+ .map((directive) {
+ if (directive is ImportDirective) {
+ return directive.prefix?.name;
+ } else {
+ return null;
+ }
+ })
+ .whereType<String>()
+ .toSet();
+ }
+
+ /// Parses the metadata.
+ Metadata parse() {
+ Timeout? timeout;
+ PlatformSelector? testOn;
+ Object? /*String|bool*/ skip;
+ Map<PlatformSelector, Metadata>? onPlatform;
+ Set<String>? tags;
+ int? retry;
+
+ for (var annotation in _annotations) {
+ var pair =
+ _resolveConstructor(annotation.name, annotation.constructorName);
+ var (name, constructorName) = pair;
+
+ if (name == 'TestOn') {
+ _assertSingle(testOn, 'TestOn', annotation);
+ testOn = _parseTestOn(annotation);
+ } else if (name == 'Timeout') {
+ _assertSingle(timeout, 'Timeout', annotation);
+ timeout = _parseTimeout(annotation, constructorName);
+ } else if (name == 'Skip') {
+ _assertSingle(skip, 'Skip', annotation);
+ skip = _parseSkip(annotation);
+ } else if (name == 'OnPlatform') {
+ _assertSingle(onPlatform, 'OnPlatform', annotation);
+ onPlatform = _parseOnPlatform(annotation);
+ } else if (name == 'Tags') {
+ _assertSingle(tags, 'Tags', annotation);
+ tags = _parseTags(annotation);
+ } else if (name == 'Retry') {
+ retry = _parseRetry(annotation);
+ }
+ }
+
+ return Metadata(
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip == null ? null : true,
+ skipReason: skip is String ? skip : null,
+ onPlatform: onPlatform,
+ tags: tags,
+ retry: retry,
+ languageVersionComment: _languageVersionComment);
+ }
+
+ /// Parses a `@TestOn` annotation.
+ ///
+ /// [annotation] is the annotation.
+ PlatformSelector _parseTestOn(Annotation annotation) =>
+ _parsePlatformSelector(annotation.arguments!.arguments.first);
+
+ /// Parses an [expression] that should contain a string representing a
+ /// [PlatformSelector].
+ PlatformSelector _parsePlatformSelector(Expression expression) {
+ var literal = _parseString(expression);
+ return _contextualize(
+ literal,
+ () => PlatformSelector.parse(literal.stringValue!)
+ ..validate(_platformVariables));
+ }
+
+ /// Parses a `@Retry` annotation.
+ ///
+ /// [annotation] is the annotation.
+ int _parseRetry(Annotation annotation) =>
+ _parseInt(annotation.arguments!.arguments.first);
+
+ /// Parses a `@Timeout` annotation.
+ ///
+ /// [annotation] is the annotation. [constructorName] is the name of the named
+ /// constructor for the annotation, if any.
+ Timeout _parseTimeout(Annotation annotation, String? constructorName) {
+ if (constructorName == 'none') {
+ return Timeout.none;
+ }
+
+ var args = annotation.arguments!.arguments;
+ if (constructorName == null) return Timeout(_parseDuration(args.first));
+ return Timeout.factor(_parseNum(args.first));
+ }
+
+ /// Parses a `Timeout` constructor.
+ Timeout _parseTimeoutConstructor(Expression constructor) {
+ var name = _findConstructorName(constructor, 'Timeout');
+ var arguments = _parseArguments(constructor);
+ if (name == null) return Timeout(_parseDuration(arguments.first));
+ if (name == 'factor') return Timeout.factor(_parseNum(arguments.first));
+ throw SourceSpanFormatException('Invalid timeout', _spanFor(constructor));
+ }
+
+ /// Parses a `@Skip` annotation.
+ ///
+ /// [annotation] is the annotation.
+ ///
+ /// Returns either `true` or a reason string.
+ dynamic _parseSkip(Annotation annotation) {
+ var args = annotation.arguments!.arguments;
+ return args.isEmpty ? true : _parseString(args.first).stringValue;
+ }
+
+ /// Parses a `Skip` constructor.
+ ///
+ /// Returns either `true` or a reason string.
+ dynamic _parseSkipConstructor(Expression constructor) {
+ _findConstructorName(constructor, 'Skip');
+ var arguments = _parseArguments(constructor);
+ return arguments.isEmpty ? true : _parseString(arguments.first).stringValue;
+ }
+
+ /// Parses a `@Tags` annotation.
+ ///
+ /// [annotation] is the annotation.
+ Set<String> _parseTags(Annotation annotation) {
+ return _parseList(annotation.arguments!.arguments.first)
+ .map((tagExpression) {
+ var name = _parseString(tagExpression).stringValue!;
+ if (name.contains(anchoredHyphenatedIdentifier)) return name;
+
+ throw SourceSpanFormatException(
+ 'Invalid tag name. Tags must be (optionally hyphenated) Dart '
+ 'identifiers.',
+ _spanFor(tagExpression));
+ }).toSet();
+ }
+
+ /// Parses an `@OnPlatform` annotation.
+ ///
+ /// [annotation] is the annotation.
+ Map<PlatformSelector, Metadata> _parseOnPlatform(Annotation annotation) {
+ return _parseMap(annotation.arguments!.arguments.first,
+ key: _parsePlatformSelector, value: (value) {
+ var expressions = <AstNode>[];
+ if (value is ListLiteral) {
+ expressions = _parseList(value);
+ } else if (value is InstanceCreationExpression ||
+ value is PrefixedIdentifier ||
+ value is MethodInvocation) {
+ expressions = [value];
+ } else {
+ throw SourceSpanFormatException(
+ 'Expected a Timeout, Skip, or List of those.', _spanFor(value));
+ }
+
+ Timeout? timeout;
+ Object? skip;
+ for (var expression in expressions) {
+ if (expression is InstanceCreationExpression) {
+ var className = expression.constructorName.type.name2.lexeme;
+
+ if (className == 'Timeout') {
+ _assertSingle(timeout, 'Timeout', expression);
+ timeout = _parseTimeoutConstructor(expression);
+ continue;
+ } else if (className == 'Skip') {
+ _assertSingle(skip, 'Skip', expression);
+ skip = _parseSkipConstructor(expression);
+ continue;
+ }
+ } else if (expression is PrefixedIdentifier &&
+ expression.prefix.name == 'Timeout') {
+ if (expression.identifier.name != 'none') {
+ throw SourceSpanFormatException(
+ 'Undefined value.', _spanFor(expression));
+ }
+
+ _assertSingle(timeout, 'Timeout', expression);
+ timeout = Timeout.none;
+ continue;
+ } else if (expression is MethodInvocation) {
+ var className =
+ _typeNameFromMethodInvocation(expression, ['Timeout', 'Skip']);
+ if (className == 'Timeout') {
+ _assertSingle(timeout, 'Timeout', expression);
+ timeout = _parseTimeoutConstructor(expression);
+ continue;
+ } else if (className == 'Skip') {
+ _assertSingle(skip, 'Skip', expression);
+ skip = _parseSkipConstructor(expression);
+ continue;
+ }
+ }
+
+ throw SourceSpanFormatException(
+ 'Expected a Timeout or Skip.', _spanFor(expression));
+ }
+
+ return Metadata.parse(timeout: timeout, skip: skip);
+ });
+ }
+
+ /// Parses a `const Duration` expression.
+ Duration _parseDuration(Expression expression) {
+ _findConstructorName(expression, 'Duration');
+
+ var arguments = _parseArguments(expression);
+ var values = _parseNamedArguments(arguments)
+ .map((key, value) => MapEntry(key, _parseInt(value)));
+
+ return Duration(
+ days: values['days'] ?? 0,
+ hours: values['hours'] ?? 0,
+ minutes: values['minutes'] ?? 0,
+ seconds: values['seconds'] ?? 0,
+ milliseconds: values['milliseconds'] ?? 0,
+ microseconds: values['microseconds'] ?? 0);
+ }
+
+ Map<String, Expression> _parseNamedArguments(
+ NodeList<Expression> arguments) =>
+ {
+ for (var a in arguments.whereType<NamedExpression>())
+ a.name.label.name: a.expression
+ };
+
+ /// Asserts that [existing] is null.
+ ///
+ /// [name] is the name of the annotation and [node] is its location, used for
+ /// error reporting.
+ void _assertSingle(Object? existing, String name, AstNode node) {
+ if (existing == null) return;
+ throw SourceSpanFormatException(
+ 'Only a single $name may be used.', _spanFor(node));
+ }
+
+ NodeList<Expression> _parseArguments(Expression expression) {
+ if (expression is InstanceCreationExpression) {
+ return expression.argumentList.arguments;
+ }
+ if (expression is MethodInvocation) {
+ return expression.argumentList.arguments;
+ }
+ throw SourceSpanFormatException(
+ 'Expected an instantiation', _spanFor(expression));
+ }
+
+ /// Resolves a constructor name from its type [identifier] and its
+ /// [constructorName].
+ ///
+ /// Since the parsed file isn't fully resolved, this is necessary to
+ /// disambiguate between prefixed names and named constructors.
+ (String, String?) _resolveConstructor(
+ Identifier identifier, SimpleIdentifier? constructorName) {
+ // The syntax is ambiguous between named constructors and prefixed
+ // annotations, so we need to resolve that ambiguity using the known
+ // prefixes. The analyzer parses "new x.y()" as prefix "x", annotation "y",
+ // and named constructor null. It parses "new x.y.z()" as prefix "x",
+ // annotation "y", and named constructor "z".
+ String className;
+ String? namedConstructor;
+ if (identifier is PrefixedIdentifier &&
+ !_prefixes.contains(identifier.prefix.name) &&
+ constructorName == null) {
+ className = identifier.prefix.name;
+ namedConstructor = identifier.identifier.name;
+ } else {
+ className = identifier is PrefixedIdentifier
+ ? identifier.identifier.name
+ : identifier.name;
+ if (constructorName != null) namedConstructor = constructorName.name;
+ }
+ return (className, namedConstructor);
+ }
+
+ /// Parses a constructor invocation for [className].
+ ///
+ /// Returns the name of the named constructor used, or null if the default
+ /// constructor is used.
+ /// If [expression] is not an instantiation of a [className] throws.
+ String? _findConstructorName(Expression expression, String className) {
+ if (expression is InstanceCreationExpression) {
+ return _findConstructorNameFromInstantiation(expression, className);
+ }
+ if (expression is MethodInvocation) {
+ return _findConstructorNameFromMethod(expression, className);
+ }
+ throw SourceSpanFormatException(
+ 'Expected a $className.', _spanFor(expression));
+ }
+
+ String? _findConstructorNameFromInstantiation(
+ InstanceCreationExpression constructor, String className) {
+ var actualClassName = constructor.constructorName.type.name2.lexeme;
+ var constructorName = constructor.constructorName.name?.name;
+
+ if (actualClassName != className) {
+ throw SourceSpanFormatException(
+ 'Expected a $className.', _spanFor(constructor));
+ }
+
+ return constructorName;
+ }
+
+ String? _findConstructorNameFromMethod(
+ MethodInvocation constructor, String className) {
+ var target = constructor.target;
+ if (target != null) {
+ // target could be an import prefix or a different class. Assume that
+ // named constructor on a different class won't match the class name we
+ // are looking for.
+ // Example: `test.Timeout()`
+ if (constructor.methodName.name == className) return null;
+ // target is an optionally prefixed class, method is named constructor
+ // Examples: `Timeout.factor(2)`, `test.Timeout.factor(2)`
+ String? parsedName;
+ if (target is SimpleIdentifier) parsedName = target.name;
+ if (target is PrefixedIdentifier) parsedName = target.identifier.name;
+ if (parsedName != className) {
+ throw SourceSpanFormatException(
+ 'Expected a $className.', _spanFor(constructor));
+ }
+ return constructor.methodName.name;
+ }
+ // No target, must be an unnamed constructor
+ // Example `Timeout()`
+ if (constructor.methodName.name != className) {
+ throw SourceSpanFormatException(
+ 'Expected a $className.', _spanFor(constructor));
+ }
+ return null;
+ }
+
+ /// Returns a type from [candidates] that _may_ be a type instantiated by
+ /// [constructor].
+ ///
+ /// This can be fooled - for instance the invocation `foo.Bar()` may look like
+ /// a prefixed instantiation of a `Bar` even though it is a named constructor
+ /// instantiation of a `foo`, or a method invocation on a variable `foo`, or
+ /// ...
+ ///
+ /// Similarly `Baz.another` may look like the named constructor invocation of
+ /// a `Baz`even though it is a prefixed instantiation of an `another`, or a
+ /// method invocation on a variable `Baz`, or ...
+ String? _typeNameFromMethodInvocation(
+ MethodInvocation constructor, List<String> candidates) {
+ var methodName = constructor.methodName.name;
+ // Examples: `Timeout()`, `test.Timeout()`
+ if (candidates.contains(methodName)) return methodName;
+ var target = constructor.target;
+ // Example: `SomeOtherClass()`
+ if (target == null) return null;
+ if (target is SimpleIdentifier) {
+ // Example: `Timeout.factor()`
+ if (candidates.contains(target.name)) return target.name;
+ }
+ if (target is PrefixedIdentifier) {
+ // Looks like `some_prefix.SomeTarget.someMethod` - "SomeTarget" is the
+ // only potential type name.
+ // Example: `test.Timeout.factor()`
+ if (candidates.contains(target.identifier.name)) {
+ return target.identifier.name;
+ }
+ }
+ return null;
+ }
+
+ /// Parses a Map literal.
+ ///
+ /// By default, returns [Expression] keys and values. These can be overridden
+ /// with the [key] and [value] parameters.
+ Map<K, V> _parseMap<K, V>(Expression expression,
+ {K Function(Expression)? key, V Function(Expression)? value}) {
+ key ??= (expression) => expression as K;
+ value ??= (expression) => expression as V;
+
+ if (expression is! SetOrMapLiteral) {
+ throw SourceSpanFormatException('Expected a Map.', _spanFor(expression));
+ }
+
+ var map = <K, V>{};
+ for (var element in expression.elements) {
+ if (element is MapLiteralEntry) {
+ map[key(element.key)] = value(element.value);
+ } else {
+ throw SourceSpanFormatException(
+ 'Expected a map entry.', _spanFor(element));
+ }
+ }
+ return map;
+ }
+
+ /// Parses a List literal.
+ List<Expression> _parseList(Expression expression) {
+ if (expression is! ListLiteral) {
+ throw SourceSpanFormatException('Expected a List.', _spanFor(expression));
+ }
+
+ var list = expression;
+
+ return list.elements.map((e) {
+ if (e is! Expression) {
+ throw SourceSpanFormatException(
+ 'Expected only literal elements.', _spanFor(e));
+ }
+ return e;
+ }).toList();
+ }
+
+ /// Parses a constant number literal.
+ num _parseNum(Expression expression) {
+ if (expression is IntegerLiteral && expression.value != null) {
+ return expression.value!;
+ }
+ if (expression is DoubleLiteral) return expression.value;
+ throw SourceSpanFormatException('Expected a number.', _spanFor(expression));
+ }
+
+ /// Parses a constant int literal.
+ int _parseInt(Expression expression) {
+ if (expression is IntegerLiteral && expression.value != null) {
+ return expression.value!;
+ }
+ throw SourceSpanFormatException(
+ 'Expected an integer.', _spanFor(expression));
+ }
+
+ /// Parses a constant String literal.
+ StringLiteral _parseString(Expression expression) {
+ if (expression is StringLiteral && expression.stringValue != null) {
+ return expression;
+ }
+ throw SourceSpanFormatException(
+ 'Expected a String literal.', _spanFor(expression));
+ }
+
+ /// Creates a [SourceSpan] for [node].
+ SourceSpan _spanFor(AstNode node) {
+ // Load a SourceFile from scratch here since we're only ever going to emit
+ // one error per file anyway.
+ return SourceFile.fromString(_contents, url: p.toUri(_path))
+ .span(node.offset, node.end);
+ }
+
+ /// Runs [fn] and contextualizes any [SourceSpanFormatException]s that occur
+ /// in it relative to [literal].
+ T _contextualize<T>(StringLiteral literal, T Function() fn) {
+ try {
+ return fn();
+ } on SourceSpanFormatException catch (error) {
+ var file = SourceFile.fromString(_contents, url: p.toUri(_path));
+ var span = contextualizeSpan(error.span!, literal, file);
+ if (span == null) rethrow;
+ throw SourceSpanFormatException(error.message, span);
+ }
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/platform.dart b/pkgs/test_core/lib/src/runner/platform.dart
new file mode 100644
index 0000000..3048052
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/platform.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2016, 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 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+
+import 'environment.dart';
+import 'runner_suite.dart';
+import 'suite.dart';
+
+/// A class that defines a platform for which test suites can be loaded.
+///
+/// A minimal plugin must define [loadChannel], which connects to a client in
+/// which the tests are defined. This is enough to support most of the test
+/// runner's functionality.
+///
+/// In order to support interactive debugging, a plugin must override [load] as
+/// well, which returns a [RunnerSuite] that can contain a custom [Environment]
+/// and control debugging metadata such as [RunnerSuite.isDebugging] and
+/// [RunnerSuite.onDebugging]. The plugin must create this suite by calling the
+/// [deserializeSuite] helper function.
+///
+/// A platform plugin can be registered by passing it to [Loader.new]'s
+/// `plugins` parameter.
+abstract class PlatformPlugin {
+ /// Loads the runner suite for the test file at [path] using [platform], with
+ /// [suiteConfig] encoding the suite-specific configuration.
+ ///
+ /// By default, this just calls [loadChannel] and passes its result to
+ /// [deserializeSuite]. However, it can be overridden to provide more
+ /// fine-grained control over the [RunnerSuite], including providing a custom
+ /// implementation of [Environment].
+ ///
+ /// Subclasses overriding this method must call [deserializeSuite] in
+ /// `platform_helpers.dart` to obtain a [RunnerSuiteController]. They must
+ /// pass the opaque [message] parameter to the [deserializeSuite] call.
+ Future<RunnerSuite?> load(String path, SuitePlatform platform,
+ SuiteConfiguration suiteConfig, Map<String, Object?> message);
+
+ Future closeEphemeral() async {}
+
+ Future close() async {}
+}
diff --git a/pkgs/test_core/lib/src/runner/plugin/customizable_platform.dart b/pkgs/test_core/lib/src/runner/plugin/customizable_platform.dart
new file mode 100644
index 0000000..0ded7ae
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/plugin/customizable_platform.dart
@@ -0,0 +1,55 @@
+// 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.
+
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:yaml/yaml.dart';
+
+import './../platform.dart';
+
+/// An interface for [PlatformPlugin]s that support per-platform customization.
+///
+/// If a [PlatformPlugin] implements this, the user will be able to override the
+/// [Runtime]s it supports using the
+/// [`override_platforms`][override_platforms] configuration field, and define
+/// new runtimes based on them using the [`define_platforms`][define_platforms]
+/// field. The custom settings will be passed to the plugin using
+/// [customizePlatform].
+///
+/// [override_platforms]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#override_platforms
+/// [define_platforms]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#define_platforms
+///
+/// Plugins that implement this **must** support children of recognized runtimes
+/// (created by [Runtime.extend]) in their [load] methods.
+abstract class CustomizablePlatform<T extends Object> extends PlatformPlugin {
+ /// Parses user-provided [settings] for a custom platform into a
+ /// plugin-defined format.
+ ///
+ /// The [settings] come from a user's configuration file. The parsed output
+ /// will be passed to [customizePlatform].
+ ///
+ /// Subclasses should throw [SourceSpanFormatException]s if [settings]
+ /// contains invalid configuration. Unrecognized fields should be ignored if
+ /// possible.
+ T parsePlatformSettings(YamlMap settings);
+
+ /// Merges [settings1] with [settings2] and returns a new settings object that
+ /// includes the configuration of both.
+ ///
+ /// When the settings conflict, [settings2] should take priority.
+ ///
+ /// This is used to merge global settings with local settings, or a custom
+ /// platform's settings with its parent's.
+ T mergePlatformSettings(T settings1, T settings2);
+
+ /// Defines user-provided [settings] for [runtime].
+ ///
+ /// The [runtime] is a runtime this plugin was declared to accept when
+ /// registered with [Loader.registerPlatformPlugin], or a runtime whose
+ /// [Runtime.parent] is one of those runtimes. Subclasses should customize the
+ /// behavior for these runtimes when [loadChannel] or [load] is called with
+ /// the given [runtime], using the [settings] which are parsed by
+ /// [parsePlatformSettings]. This is guaranteed to be called before either
+ /// `load` method.
+ void customizePlatform(Runtime runtime, T settings);
+}
diff --git a/pkgs/test_core/lib/src/runner/plugin/environment.dart b/pkgs/test_core/lib/src/runner/plugin/environment.dart
new file mode 100644
index 0000000..7558d62
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/plugin/environment.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, 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 'package:async/async.dart';
+
+import '../environment.dart';
+
+/// The default environment for platform plugins.
+class PluginEnvironment implements Environment {
+ @override
+ final supportsDebugging = false;
+ @override
+ Stream<void> get onRestart => StreamController<void>.broadcast().stream;
+
+ const PluginEnvironment();
+
+ @override
+ Uri? get observatoryUrl => null;
+
+ @override
+ Uri? get remoteDebuggerUrl => null;
+
+ @override
+ CancelableOperation displayPause() => throw UnsupportedError(
+ 'PluginEnvironment.displayPause is not supported.');
+}
diff --git a/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart b/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
new file mode 100644
index 0000000..450dedb
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
@@ -0,0 +1,162 @@
+// Copyright (c) 2016, 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:io';
+
+import 'package:stack_trace/stack_trace.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart'
+ show Metadata, RemoteException, SuitePlatform;
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
+
+import '../configuration.dart';
+import '../environment.dart';
+import '../load_exception.dart';
+import '../runner_suite.dart';
+import '../runner_test.dart';
+import '../suite.dart';
+
+/// A helper method for creating a [RunnerSuiteController] containing tests
+/// that communicate over [channel].
+///
+/// This returns a controller so that the caller has a chance to control the
+/// runner suite's debugging state based on plugin-specific logic.
+///
+/// If the suite is closed, this will close [channel].
+///
+/// The [message] parameter is an opaque object passed from the runner to
+/// [PlatformPlugin.load]. Plugins shouldn't interact with it other than to pass
+/// it on to [deserializeSuite].
+///
+/// If [mapper] is passed, it will be used to adjust stack traces for any errors
+/// emitted by tests.
+///
+/// [gatherCoverage] is a callback which returns a hit-map containing merged
+/// coverage report suitable for use with `package:coverage`.
+RunnerSuiteController deserializeSuite(
+ String path,
+ SuitePlatform platform,
+ SuiteConfiguration suiteConfig,
+ Environment environment,
+ StreamChannel<Object?> channel,
+ Object /*Map<String, Object?>*/ message,
+ {Future<Map<String, dynamic>> Function()? gatherCoverage}) {
+ var disconnector = Disconnector<Object?>();
+ var suiteChannel = MultiChannel<Object?>(channel.transform(disconnector));
+
+ suiteChannel.sink.add(<String, Object?>{
+ 'type': 'initial',
+ 'platform': platform.serialize(),
+ 'metadata': suiteConfig.metadata.serialize(),
+ 'asciiGlyphs': Platform.isWindows,
+ 'path': path,
+ 'collectTraces': Configuration.current.reporter == 'json' ||
+ Configuration.current.fileReporters.containsKey('json') ||
+ suiteConfig.testSelections.any(
+ (selection) => selection.line != null || selection.col != null),
+ 'noRetry': Configuration.current.noRetry,
+ 'foldTraceExcept': Configuration.current.foldTraceExcept.toList(),
+ 'foldTraceOnly': Configuration.current.foldTraceOnly.toList(),
+ 'allowDuplicateTestNames': suiteConfig.allowDuplicateTestNames,
+ 'ignoreTimeouts': suiteConfig.ignoreTimeouts,
+ ...message as Map<String, dynamic>,
+ });
+
+ var completer = Completer<Group>();
+
+ var loadSuiteZone = Zone.current;
+ void handleError(Object error, StackTrace stackTrace) {
+ disconnector.disconnect();
+
+ if (completer.isCompleted) {
+ // If we've already provided a controller, send the error to the
+ // LoadSuite. This will cause the virtual load test to fail, which will
+ // notify the user of the error.
+ loadSuiteZone.handleUncaughtError(error, stackTrace);
+ } else {
+ completer.completeError(error, stackTrace);
+ }
+ }
+
+ suiteChannel.stream.cast<Map<String, Object?>>().listen(
+ (response) {
+ switch (response['type'] as String) {
+ case 'print':
+ print(response['line']);
+ break;
+
+ case 'loadException':
+ handleError(LoadException(path, response['message'] as Object),
+ Trace.current());
+ break;
+
+ case 'error':
+ var asyncError = RemoteException.deserialize(
+ response['error'] as Map<String, dynamic>);
+ handleError(
+ LoadException(path, asyncError.error), asyncError.stackTrace);
+ break;
+
+ case 'success':
+ var deserializer = _Deserializer(suiteChannel);
+ completer.complete(
+ deserializer.deserializeGroup(response['root'] as Map));
+ break;
+ }
+ },
+ onError: handleError,
+ onDone: () {
+ if (completer.isCompleted) return;
+ completer.completeError(
+ LoadException(path, 'Connection closed before test suite loaded.'),
+ Trace.current());
+ });
+
+ return RunnerSuiteController(
+ environment, suiteConfig, suiteChannel, completer.future, platform,
+ path: path,
+ onClose: () => disconnector.disconnect().onError(handleError),
+ gatherCoverage: gatherCoverage);
+}
+
+/// A utility class for storing state while deserializing tests.
+class _Deserializer {
+ /// The channel over which tests communicate.
+ final MultiChannel _channel;
+
+ _Deserializer(this._channel);
+
+ /// Deserializes [group] into a concrete [Group].
+ Group deserializeGroup(Map group) {
+ var metadata = Metadata.deserialize(group['metadata'] as Map);
+ return Group(
+ group['name'] as String,
+ (group['entries'] as List).map((entry) {
+ var map = entry as Map;
+ if (map['type'] == 'group') return deserializeGroup(map);
+ return _deserializeTest(map)!;
+ }),
+ metadata: metadata,
+ trace: group['trace'] == null
+ ? null
+ : Trace.parse(group['trace'] as String),
+ setUpAll: _deserializeTest(group['setUpAll'] as Map?),
+ tearDownAll: _deserializeTest(group['tearDownAll'] as Map?));
+ }
+
+ /// Deserializes [test] into a concrete [Test] class.
+ ///
+ /// Returns `null` if [test] is `null`.
+ Test? _deserializeTest(Map? test) {
+ if (test == null) return null;
+
+ var metadata = Metadata.deserialize(test['metadata'] as Map);
+ var trace =
+ test['trace'] == null ? null : Trace.parse(test['trace'] as String);
+ var testChannel = _channel.virtualChannel((test['channel'] as num).toInt());
+ return RunnerTest(test['name'] as String, metadata, trace, testChannel);
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart b/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
new file mode 100644
index 0000000..8eb2b76
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2016, 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 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart'
+ show RemoteListener, StackTraceFormatter, StackTraceMapper;
+
+/// Returns a channel that will emit a serialized representation of the tests
+/// defined in [getMain].
+///
+/// This channel is used to control the tests. Platform plugins should forward
+/// it `deserializeSuite`. It's guaranteed to communicate using only
+/// JSON-serializable values.
+///
+/// Any errors thrown within [getMain], synchronously or not, will be forwarded
+/// to the load test for this suite. Prints will similarly be forwarded to that
+/// test's print stream.
+///
+/// If [hidePrints] is `true` (the default), calls to `print()` within this
+/// suite will not be forwarded to the parent zone's print handler. However, the
+/// caller may want them to be forwarded in (for example) a browser context
+/// where they'll be visible in the development console.
+///
+/// If [beforeLoad] is passed, it's called before the tests have been declared
+/// for this worker.
+StreamChannel<Object?> serializeSuite(Function Function() getMain,
+ {bool hidePrints = true,
+ Future Function(
+ StreamChannel<Object?> Function(String name) suiteChannel)?
+ beforeLoad}) =>
+ RemoteListener.start(
+ getMain,
+ hidePrints: hidePrints,
+ beforeLoad: beforeLoad,
+ );
+
+/// Sets the stack trace mapper for the current test suite.
+///
+/// This is used to convert JavaScript stack traces into their Dart equivalents
+/// using source maps. It should be set before any tests run, usually in the
+/// `onLoad()` callback to [serializeSuite].
+void setStackTraceMapper(StackTraceMapper mapper) {
+ var formatter = StackTraceFormatter.current;
+ if (formatter == null) {
+ throw StateError(
+ 'setStackTraceMapper() may only be called within a test worker.');
+ }
+
+ formatter.configure(mapper: mapper);
+}
diff --git a/pkgs/test_core/lib/src/runner/plugin/shared_platform_helpers.dart b/pkgs/test_core/lib/src/runner/plugin/shared_platform_helpers.dart
new file mode 100644
index 0000000..4fdcf23
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/plugin/shared_platform_helpers.dart
@@ -0,0 +1,21 @@
+// 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:convert';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+/// Converts a raw [Socket] into a [StreamChannel] of JSON objects.
+///
+/// JSON messages are separated by newlines.
+StreamChannel<Object?> jsonSocketStreamChannel(Socket socket) =>
+ StreamChannel.withGuarantees(socket, socket)
+ .cast<List<int>>()
+ .transform(StreamChannelTransformer.fromCodec(utf8))
+ .transformStream(const LineSplitter())
+ .transformSink(StreamSinkTransformer.fromHandlers(
+ handleData: (original, sink) => sink.add('$original\n')))
+ .transform(jsonDocument);
diff --git a/pkgs/test_core/lib/src/runner/reporter.dart b/pkgs/test_core/lib/src/runner/reporter.dart
new file mode 100644
index 0000000..b179337
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/reporter.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2015, 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.
+
+/// An interface for classes that watch the progress of an Engine and report it
+/// to the user.
+///
+/// A reporter should subscribe to the Engine's events as soon as it's created.
+abstract class Reporter {
+ /// Pauses the reporter's output.
+ ///
+ /// Subclasses should buffer any events from the engine while they're paused.
+ /// They should also ensure that this does nothing if the reporter is already
+ /// paused.
+ void pause();
+
+ /// Resumes the reporter's output after being [paused].
+ ///
+ /// Subclasses should ensure that this does nothing if the reporter isn't
+ /// paused.
+ void resume();
+}
+
+/// A reporter that prints nothing.
+class SilentReporter implements Reporter {
+ @override
+ void pause() {}
+
+ @override
+ void resume() {}
+}
diff --git a/pkgs/test_core/lib/src/runner/reporter/compact.dart b/pkgs/test_core/lib/src/runner/reporter/compact.dart
new file mode 100644
index 0000000..13ccd11
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/reporter/compact.dart
@@ -0,0 +1,446 @@
+// Copyright (c) 2015, 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:io';
+import 'dart:isolate';
+
+import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/message.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
+
+import '../../util/io.dart';
+import '../../util/pretty_print.dart';
+import '../../util/pretty_print.dart' as utils;
+import '../engine.dart';
+import '../load_exception.dart';
+import '../load_suite.dart';
+import '../reporter.dart';
+
+/// A reporter that prints test results to the console in a single
+/// continuously-updating line.
+class CompactReporter implements Reporter {
+ /// Whether the reporter should emit terminal color escapes.
+ final bool _color;
+
+ /// The terminal escape for green text, or the empty string if this is Windows
+ /// or not outputting to a terminal.
+ final String _green;
+
+ /// The terminal escape for red text, or the empty string if this is Windows
+ /// or not outputting to a terminal.
+ final String _red;
+
+ /// The terminal escape for yellow text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _yellow;
+
+ /// The terminal escape for gray text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _gray;
+
+ /// The terminal escape code for cyan text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _cyan;
+
+ /// The terminal escape for bold text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _bold;
+
+ /// The terminal escape for removing test coloring, or the empty string if
+ /// this is Windows or not outputting to a terminal.
+ final String _noColor;
+
+ /// The engine used to run the tests.
+ final Engine _engine;
+
+ /// Whether the path to each test's suite should be printed.
+ final bool _printPath;
+
+ /// Whether the platform each test is running on should be printed.
+ final bool _printPlatform;
+
+ /// A stopwatch that tracks the duration of the full run.
+ final _stopwatch = Stopwatch();
+
+ /// Whether we've started [_stopwatch].
+ ///
+ /// We can't just use `_stopwatch.isRunning` because the stopwatch is stopped
+ /// when the reporter is paused.
+ var _stopwatchStarted = false;
+
+ /// The size of `_engine.passed` last time a progress notification was
+ /// printed.
+ int _lastProgressPassed = 0;
+
+ /// The size of `_engine.skipped` last time a progress notification was
+ /// printed.
+ int? _lastProgressSkipped;
+
+ /// The size of `_engine.failed` last time a progress notification was
+ /// printed.
+ int? _lastProgressFailed;
+
+ /// The duration of the test run in seconds last time a progress notification
+ /// was printed.
+ int? _lastProgressElapsed;
+
+ /// The message printed for the last progress notification.
+ String? _lastProgressMessage;
+
+ /// The suffix added to the last progress notification.
+ String? _lastProgressSuffix;
+
+ /// Whether the message printed for the last progress notification was
+ /// truncated.
+ bool? _lastProgressTruncated;
+
+ // Whether a newline has been printed since the last progress line.
+ var _printedNewline = true;
+
+ /// Whether the reporter is paused.
+ var _paused = false;
+
+ // Whether a notice should be logged about enabling stack trace chaining at
+ // the end of all tests running.
+ var _shouldPrintStackTraceChainingNotice = false;
+
+ /// The set of all subscriptions to various streams.
+ final _subscriptions = <StreamSubscription>{};
+
+ final StringSink _sink;
+
+ /// Watches the tests run by [engine] and prints their results to the
+ /// terminal.
+ ///
+ /// If [color] is `true`, this will use terminal colors; if it's `false`, it
+ /// won't. If [printPath] is `true`, this will print the path name as part of
+ /// the test description. Likewise, if [printPlatform] is `true`, this will
+ /// print the platform as part of the test description.
+ static CompactReporter watch(Engine engine, StringSink sink,
+ {required bool color,
+ required bool printPath,
+ required bool printPlatform}) =>
+ CompactReporter._(engine, sink,
+ color: color, printPath: printPath, printPlatform: printPlatform);
+
+ CompactReporter._(this._engine, this._sink,
+ {required bool color,
+ required bool printPath,
+ required bool printPlatform})
+ : _printPath = printPath,
+ _printPlatform = printPlatform,
+ _color = color,
+ _green = color ? '\u001b[32m' : '',
+ _red = color ? '\u001b[31m' : '',
+ _yellow = color ? '\u001b[33m' : '',
+ _gray = color ? '\u001b[90m' : '',
+ _cyan = color ? '\u001b[36m' : '',
+ _bold = color ? '\u001b[1m' : '',
+ _noColor = color ? '\u001b[0m' : '' {
+ _subscriptions.add(_engine.onTestStarted.listen(_onTestStarted));
+
+ // Convert the future to a stream so that the subscription can be paused or
+ // canceled.
+ _subscriptions.add(_engine.success.asStream().listen(_onDone));
+ }
+
+ @override
+ void pause() {
+ if (_paused) return;
+ _paused = true;
+
+ if (!_printedNewline) _sink.writeln('');
+ _printedNewline = true;
+ _stopwatch.stop();
+
+ // Force the next message to be printed, even if it's identical to the
+ // previous one. If the reporter was paused, text was probably printed
+ // during the pause.
+ _lastProgressMessage = null;
+
+ for (var subscription in _subscriptions) {
+ subscription.pause();
+ }
+ }
+
+ @override
+ void resume() {
+ if (!_paused) return;
+ _paused = false;
+
+ if (_stopwatchStarted) _stopwatch.start();
+
+ for (var subscription in _subscriptions) {
+ subscription.resume();
+ }
+ }
+
+ void _cancel() {
+ for (var subscription in _subscriptions) {
+ subscription.cancel();
+ }
+ _subscriptions.clear();
+ }
+
+ /// A callback called when the engine begins running [liveTest].
+ void _onTestStarted(LiveTest liveTest) {
+ if (!_stopwatchStarted) {
+ _stopwatchStarted = true;
+ _stopwatch.start();
+
+ // Keep updating the time even when nothing else is happening.
+ _subscriptions.add(Stream<void>.periodic(const Duration(seconds: 1))
+ .listen((_) => _progressLine(_lastProgressMessage ?? '')));
+ }
+
+ // If this is the first test or suite load to start, print a progress line
+ // so the user knows what's running.
+ if ((_engine.active.length == 1 && _engine.active.first == liveTest) ||
+ (_engine.active.isEmpty &&
+ _engine.activeSuiteLoads.length == 1 &&
+ _engine.activeSuiteLoads.first == liveTest)) {
+ _progressLine(_description(liveTest));
+ }
+
+ _subscriptions.add(liveTest.onStateChange
+ .listen((state) => _onStateChange(liveTest, state)));
+
+ _subscriptions.add(liveTest.onError
+ .listen((error) => _onError(liveTest, error.error, error.stackTrace)));
+
+ _subscriptions.add(liveTest.onMessage.listen((message) {
+ _progressLine(_description(liveTest), truncate: false);
+ if (!_printedNewline) _sink.writeln('');
+ _printedNewline = true;
+
+ var text = message.text;
+ if (message.type == MessageType.skip) text = ' $_yellow$text$_noColor';
+ _sink.writeln(text);
+ }));
+
+ liveTest.onComplete.then((_) {
+ var result = liveTest.state.result;
+ if (result != Result.error && result != Result.failure) return;
+ var quotedName = Platform.isWindows
+ ? '"${liveTest.test.name.replaceAll('"', '"""')}"'
+ : "'${liveTest.test.name.replaceAll("'", r"'\''")}'";
+ _sink.writeln('');
+ _sink.writeln('$_bold${_cyan}To run this test again:$_noColor '
+ '${Platform.executable} test ${liveTest.suite.path} '
+ '-p ${liveTest.suite.platform.runtime.identifier} '
+ '--plain-name $quotedName');
+ });
+ }
+
+ /// A callback called when [liveTest]'s state becomes [state].
+ void _onStateChange(LiveTest liveTest, State state) {
+ if (state.status != Status.complete) return;
+
+ // Errors are printed in [onError]; no need to print them here as well.
+ if (state.result == Result.failure) return;
+ if (state.result == Result.error) return;
+
+ // Always display the name of the oldest active test, unless testing
+ // is finished in which case display the last test to complete.
+ if (_engine.active.isEmpty) {
+ _progressLine(_description(liveTest));
+ } else {
+ _progressLine(_description(_engine.active.first));
+ }
+ }
+
+ /// A callback called when [liveTest] throws [error].
+ void _onError(LiveTest liveTest, Object error, StackTrace stackTrace) {
+ if (!liveTest.test.metadata.chainStackTraces &&
+ !liveTest.suite.isLoadSuite) {
+ _shouldPrintStackTraceChainingNotice = true;
+ }
+
+ if (liveTest.state.status != Status.complete) return;
+
+ _progressLine(_description(liveTest),
+ truncate: false, suffix: ' $_bold$_red[E]$_noColor');
+ if (!_printedNewline) _sink.writeln('');
+ _printedNewline = true;
+
+ if (error is! LoadException) {
+ _sink.writeln(indent(error.toString()));
+ _sink.writeln(indent('$stackTrace'));
+ return;
+ }
+
+ // TODO - what type is this?
+ _sink.writeln(indent(error.toString(color: _color)));
+
+ // Only print stack traces for load errors that come from the user's code.
+ if (error.innerError is! IOException &&
+ error.innerError is! IsolateSpawnException &&
+ error.innerError is! FormatException &&
+ error.innerError is! String) {
+ _sink.writeln(indent('$stackTrace'));
+ }
+ }
+
+ /// A callback called when the engine is finished running tests.
+ ///
+ /// [success] will be `true` if all tests passed, `false` if some tests
+ /// failed, and `null` if the engine was closed prematurely.
+ void _onDone(bool? success) {
+ _cancel();
+ _stopwatch.stop();
+
+ // A null success value indicates that the engine was closed before the
+ // tests finished running, probably because of a signal from the user. We
+ // shouldn't print summary information, we should just make sure the
+ // terminal cursor is on its own line.
+ if (success == null) {
+ if (!_printedNewline) _sink.writeln('');
+ _printedNewline = true;
+ return;
+ }
+
+ if (_engine.liveTests.isEmpty) {
+ if (!_printedNewline) _sink.write('\r');
+ var message = 'No tests ran.';
+ _sink.write(message);
+
+ // Add extra padding to overwrite any load messages.
+ if (!_printedNewline) _sink.write(' ' * (lineLength - message.length));
+ _sink.writeln('');
+ } else if (!success) {
+ for (var liveTest in _engine.active) {
+ _progressLine(_description(liveTest),
+ truncate: false,
+ suffix: ' - did not complete $_bold$_red[E]$_noColor');
+ _sink.writeln('');
+ }
+ _progressLine('Some tests failed.', color: _red);
+ _sink.writeln('');
+ } else if (_engine.passed.isEmpty) {
+ _progressLine('All tests skipped.');
+ _sink.writeln('');
+ } else {
+ _progressLine('All tests passed!');
+ _sink.writeln('');
+ }
+
+ if (_shouldPrintStackTraceChainingNotice) {
+ _sink
+ ..writeln('')
+ ..writeln('Consider enabling the flag chain-stack-traces to '
+ 'receive more detailed exceptions.\n'
+ "For example, 'dart test --chain-stack-traces'.");
+ }
+ }
+
+ /// Prints a line representing the current state of the tests.
+ ///
+ /// [message] goes after the progress report, and may be truncated to fit the
+ /// entire line within [lineLength]. If [color] is passed, it's used as the
+ /// color for [message]. If [suffix] is passed, it's added to the end of
+ /// [message].
+ bool _progressLine(String message,
+ {String? color, bool truncate = true, String? suffix}) {
+ var elapsed = _stopwatch.elapsed.inSeconds;
+
+ // Print nothing if nothing has changed since the last progress line.
+ if (_engine.passed.length == _lastProgressPassed &&
+ _engine.skipped.length == _lastProgressSkipped &&
+ _engine.failed.length == _lastProgressFailed &&
+ message == _lastProgressMessage &&
+ // Don't re-print just because a suffix was removed.
+ (suffix == null || suffix == _lastProgressSuffix) &&
+ // Don't re-print just because the message became re-truncated, because
+ // that doesn't add information.
+ (truncate || !_lastProgressTruncated!) &&
+ // If we printed a newline, that means the last line *wasn't* a progress
+ // line. In that case, we don't want to print a new progress line just
+ // because the elapsed time changed.
+ (_printedNewline || elapsed == _lastProgressElapsed)) {
+ return false;
+ }
+
+ _lastProgressPassed = _engine.passed.length;
+ _lastProgressSkipped = _engine.skipped.length;
+ _lastProgressFailed = _engine.failed.length;
+ _lastProgressElapsed = elapsed;
+ _lastProgressMessage = message;
+ _lastProgressSuffix = suffix;
+ _lastProgressTruncated = truncate;
+
+ if (suffix != null) message += suffix;
+ color ??= '';
+ var duration = _stopwatch.elapsed;
+ var buffer = StringBuffer();
+
+ // \r moves back to the beginning of the current line.
+ buffer.write('\r${_timeString(duration)} ');
+ buffer.write(_green);
+ buffer.write('+');
+ buffer.write(_engine.passed.length);
+ buffer.write(_noColor);
+
+ if (_engine.skipped.isNotEmpty) {
+ buffer.write(_yellow);
+ buffer.write(' ~');
+ buffer.write(_engine.skipped.length);
+ buffer.write(_noColor);
+ }
+
+ if (_engine.failed.isNotEmpty) {
+ buffer.write(_red);
+ buffer.write(' -');
+ buffer.write(_engine.failed.length);
+ buffer.write(_noColor);
+ }
+
+ buffer.write(': ');
+ buffer.write(color);
+
+ // Ensure the line fits within [lineLength]. [buffer] includes the color
+ // escape sequences too. Because these sequences are not visible characters,
+ // we make sure they are not counted towards the limit.
+ var length = withoutColors(buffer.toString()).length;
+ if (truncate) message = utils.truncate(message, lineLength - length);
+ buffer.write(message);
+ buffer.write(_noColor);
+
+ // Pad the rest of the line so that it looks erased.
+ buffer.write(' ' * (lineLength - withoutColors(buffer.toString()).length));
+ _sink.write(buffer.toString());
+
+ _printedNewline = false;
+ return true;
+ }
+
+ /// Returns a representation of [duration] as `MM:SS`.
+ String _timeString(Duration duration) {
+ return "${duration.inMinutes.toString().padLeft(2, '0')}:"
+ "${(duration.inSeconds % 60).toString().padLeft(2, '0')}";
+ }
+
+ /// Returns a description of [liveTest].
+ ///
+ /// This differs from the test's own description in that it may also include
+ /// the suite's name.
+ String _description(LiveTest liveTest) {
+ var name = liveTest.test.name;
+
+ if (_printPath &&
+ liveTest.suite is! LoadSuite &&
+ liveTest.suite.path != null) {
+ name = '${liveTest.suite.path}: $name';
+ }
+
+ if (_printPlatform) {
+ name = '[${liveTest.suite.platform.runtime.name}, '
+ '${liveTest.suite.platform.compiler.name}] $name';
+ }
+
+ if (liveTest.suite is LoadSuite) name = '$_bold$_gray$name$_noColor';
+
+ return name;
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/reporter/expanded.dart b/pkgs/test_core/lib/src/runner/reporter/expanded.dart
new file mode 100644
index 0000000..be2016c
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/reporter/expanded.dart
@@ -0,0 +1,347 @@
+// Copyright (c) 2015, 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 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/message.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
+
+import '../../util/pretty_print.dart';
+import '../engine.dart';
+import '../load_exception.dart';
+import '../load_suite.dart';
+import '../reporter.dart';
+
+/// A reporter that prints each test on its own line.
+///
+/// This is currently used in place of [CompactReporter] by `lib/test.dart`,
+/// which can't transitively import `dart:io` but still needs access to a runner
+/// so that test files can be run directly. This means that until issue 6943 is
+/// fixed, this must not import `dart:io`.
+class ExpandedReporter implements Reporter {
+ /// Whether the reporter should emit terminal color escapes.
+ final bool _color;
+
+ /// The terminal escape for green text, or the empty string if this is Windows
+ /// or not outputting to a terminal.
+ final String _green;
+
+ /// The terminal escape for red text, or the empty string if this is Windows
+ /// or not outputting to a terminal.
+ final String _red;
+
+ /// The terminal escape for yellow text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _yellow;
+
+ /// The terminal escape for gray text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _gray;
+
+ /// The terminal escape for bold text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _bold;
+
+ /// The terminal escape for removing test coloring, or the empty string if
+ /// this is Windows or not outputting to a terminal.
+ final String _noColor;
+
+ /// The engine used to run the tests.
+ final Engine _engine;
+
+ /// Whether the path to each test's suite should be printed.
+ final bool _printPath;
+
+ /// Whether the platform each test is running on should be printed.
+ final bool _printPlatform;
+
+ /// A stopwatch that tracks the duration of the full run.
+ final _stopwatch = Stopwatch();
+
+ /// The size of `_engine.passed` last time a progress notification was
+ /// printed.
+ int _lastProgressPassed = 0;
+
+ /// The size of `_engine.skipped` last time a progress notification was
+ /// printed.
+ int _lastProgressSkipped = 0;
+
+ /// The size of `_engine.failed` last time a progress notification was
+ /// printed.
+ int _lastProgressFailed = 0;
+
+ /// The message printed for the last progress notification.
+ String _lastProgressMessage = '';
+
+ /// The suffix added to the last progress notification.
+ String? _lastProgressSuffix;
+
+ /// Whether the reporter is paused.
+ var _paused = false;
+
+ // Whether a notice should be logged about enabling stack trace chaining at
+ // the end of all tests running.
+ var _shouldPrintStackTraceChainingNotice = false;
+
+ /// The set of all subscriptions to various streams.
+ final _subscriptions = <StreamSubscription>{};
+
+ final StringSink _sink;
+
+ /// Watches the tests run by [engine] and prints their results to the
+ /// terminal.
+ ///
+ /// If [color] is `true`, this will use terminal colors; if it's `false`, it
+ /// won't. If [printPath] is `true`, this will print the path name as part of
+ /// the test description. Likewise, if [printPlatform] is `true`, this will
+ /// print the platform as part of the test description.
+ static ExpandedReporter watch(Engine engine, StringSink sink,
+ {required bool color,
+ required bool printPath,
+ required bool printPlatform}) =>
+ ExpandedReporter._(engine, sink,
+ color: color, printPath: printPath, printPlatform: printPlatform);
+
+ ExpandedReporter._(this._engine, this._sink,
+ {required bool color,
+ required bool printPath,
+ required bool printPlatform})
+ : _printPath = printPath,
+ _printPlatform = printPlatform,
+ _color = color,
+ _green = color ? '\u001b[32m' : '',
+ _red = color ? '\u001b[31m' : '',
+ _yellow = color ? '\u001b[33m' : '',
+ _gray = color ? '\u001b[90m' : '',
+ _bold = color ? '\u001b[1m' : '',
+ _noColor = color ? '\u001b[0m' : '' {
+ _subscriptions.add(_engine.onTestStarted.listen(_onTestStarted));
+
+ // Convert the future to a stream so that the subscription can be paused or
+ // canceled.
+ _subscriptions.add(_engine.success.asStream().listen(_onDone));
+ }
+
+ @override
+ void pause() {
+ if (_paused) return;
+ _paused = true;
+
+ _stopwatch.stop();
+
+ for (var subscription in _subscriptions) {
+ subscription.pause();
+ }
+ }
+
+ @override
+ void resume() {
+ if (!_paused) return;
+
+ _stopwatch.start();
+
+ for (var subscription in _subscriptions) {
+ subscription.resume();
+ }
+ }
+
+ void _cancel() {
+ for (var subscription in _subscriptions) {
+ subscription.cancel();
+ }
+ _subscriptions.clear();
+ }
+
+ /// A callback called when the engine begins running [liveTest].
+ void _onTestStarted(LiveTest liveTest) {
+ if (liveTest.suite is! LoadSuite) {
+ if (!_stopwatch.isRunning) _stopwatch.start();
+
+ // If this is the first non-load test to start, print a progress line so
+ // the user knows what's running.
+ if (_engine.active.length == 1) _progressLine(_description(liveTest));
+
+ // The engine surfaces load tests when there are no other tests running,
+ // but because the expanded reporter's output is always visible, we don't
+ // emit information about them unless they fail.
+ _subscriptions.add(liveTest.onStateChange
+ .listen((state) => _onStateChange(liveTest, state)));
+ } else if (_engine.active.isEmpty &&
+ _engine.activeSuiteLoads.length == 1 &&
+ _engine.activeSuiteLoads.first == liveTest &&
+ liveTest.test.name.startsWith('loading ')) {
+ // Print a progress line for ongoing suite loading synthetic test since it
+ // may be slow (or stuck) depending on the platform.
+ _progressLine(_description(liveTest));
+ }
+
+ _subscriptions.add(liveTest.onError
+ .listen((error) => _onError(liveTest, error.error, error.stackTrace)));
+
+ _subscriptions.add(liveTest.onMessage.listen((message) {
+ _progressLine(_description(liveTest));
+ var text = message.text;
+ if (message.type == MessageType.skip) text = ' $_yellow$text$_noColor';
+ _sink.writeln(text);
+ }));
+ }
+
+ /// A callback called when [liveTest]'s state becomes [state].
+ void _onStateChange(LiveTest liveTest, State state) {
+ if (state.status != Status.complete) return;
+
+ // If any tests are running, display the name of the oldest active
+ // test.
+ if (_engine.active.isNotEmpty) {
+ _progressLine(_description(_engine.active.first));
+ }
+ }
+
+ /// A callback called when [liveTest] throws [error].
+ void _onError(LiveTest liveTest, Object error, StackTrace stackTrace) {
+ if (!liveTest.test.metadata.chainStackTraces &&
+ !liveTest.suite.isLoadSuite) {
+ _shouldPrintStackTraceChainingNotice = true;
+ }
+
+ if (liveTest.state.status != Status.complete) return;
+
+ _progressLine(_description(liveTest), suffix: ' $_bold$_red[E]$_noColor');
+
+ if (error is! LoadException) {
+ _sink
+ ..writeln(indent('$error'))
+ ..writeln(indent('$stackTrace'));
+ return;
+ }
+
+ // TODO - what type is this?
+ _sink.writeln(indent(error.toString(color: _color)));
+
+ // Only print stack traces for load errors that come from the user's code.
+ if (error.innerError is! FormatException && error.innerError is! String) {
+ _sink.writeln(indent('$stackTrace'));
+ }
+ }
+
+ /// A callback called when the engine is finished running tests.
+ ///
+ /// [success] will be `true` if all tests passed, `false` if some tests
+ /// failed, and `null` if the engine was closed prematurely.
+ void _onDone(bool? success) {
+ _cancel();
+ // A null success value indicates that the engine was closed before the
+ // tests finished running, probably because of a signal from the user, in
+ // which case we shouldn't print summary information.
+ if (success == null) return;
+
+ if (_engine.liveTests.isEmpty) {
+ _sink.writeln('No tests ran.');
+ } else if (!success) {
+ for (var liveTest in _engine.active) {
+ _progressLine(_description(liveTest),
+ suffix: ' - did not complete $_bold$_red[E]$_noColor');
+ }
+ _progressLine('Some tests failed.', color: _red);
+ } else if (_engine.passed.isEmpty) {
+ _progressLine('All tests skipped.');
+ } else {
+ _progressLine('All tests passed!');
+ }
+
+ if (_shouldPrintStackTraceChainingNotice) {
+ _sink
+ ..writeln('')
+ ..writeln('Consider enabling the flag chain-stack-traces to '
+ 'receive more detailed exceptions.\n'
+ "For example, 'dart test --chain-stack-traces'.");
+ }
+ }
+
+ /// Prints a line representing the current state of the tests.
+ ///
+ /// [message] goes after the progress report. If [color] is passed, it's used
+ /// as the color for [message]. If [suffix] is passed, it's added to the end
+ /// of [message].
+ void _progressLine(String message, {String? color, String? suffix}) {
+ // Print nothing if nothing has changed since the last progress line.
+ if (_engine.passed.length == _lastProgressPassed &&
+ _engine.skipped.length == _lastProgressSkipped &&
+ _engine.failed.length == _lastProgressFailed &&
+ message == _lastProgressMessage &&
+ // Don't re-print just because a suffix was removed.
+ (suffix == null || suffix == _lastProgressSuffix)) {
+ return;
+ }
+
+ _lastProgressPassed = _engine.passed.length;
+ _lastProgressSkipped = _engine.skipped.length;
+ _lastProgressFailed = _engine.failed.length;
+ _lastProgressMessage = message;
+ _lastProgressSuffix = suffix;
+
+ if (suffix != null) message += suffix;
+ color ??= '';
+ var duration = _stopwatch.elapsed;
+ var buffer = StringBuffer();
+
+ // \r moves back to the beginning of the current line.
+ buffer.write('${_timeString(duration)} ');
+ buffer.write(_green);
+ buffer.write('+');
+ buffer.write(_engine.passed.length);
+ buffer.write(_noColor);
+
+ if (_engine.skipped.isNotEmpty) {
+ buffer.write(_yellow);
+ buffer.write(' ~');
+ buffer.write(_engine.skipped.length);
+ buffer.write(_noColor);
+ }
+
+ if (_engine.failed.isNotEmpty) {
+ buffer.write(_red);
+ buffer.write(' -');
+ buffer.write(_engine.failed.length);
+ buffer.write(_noColor);
+ }
+
+ buffer.write(': ');
+ buffer.write(color);
+ buffer.write(message);
+ buffer.write(_noColor);
+
+ _sink.writeln(buffer.toString());
+ }
+
+ /// Returns a representation of [duration] as `MM:SS`.
+ String _timeString(Duration duration) {
+ return "${duration.inMinutes.toString().padLeft(2, '0')}:"
+ "${(duration.inSeconds % 60).toString().padLeft(2, '0')}";
+ }
+
+ /// Returns a description of [liveTest].
+ ///
+ /// This differs from the test's own description in that it may also include
+ /// the suite's name.
+ String _description(LiveTest liveTest) {
+ var name = liveTest.test.name;
+
+ if (_printPath &&
+ liveTest.suite is! LoadSuite &&
+ liveTest.suite.path != null) {
+ name = '${liveTest.suite.path}: $name';
+ }
+
+ if (_printPlatform) {
+ name = '[${liveTest.suite.platform.runtime.name}, '
+ '${liveTest.suite.platform.compiler.name}] $name';
+ }
+
+ if (liveTest.suite is LoadSuite) name = '$_bold$_gray$name$_noColor';
+
+ return name;
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/reporter/failures_only.dart b/pkgs/test_core/lib/src/runner/reporter/failures_only.dart
new file mode 100644
index 0000000..b6b85e7
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/reporter/failures_only.dart
@@ -0,0 +1,295 @@
+// Copyright (c) 2024, 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 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/message.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
+
+import '../../util/pretty_print.dart';
+import '../engine.dart';
+import '../load_exception.dart';
+import '../load_suite.dart';
+import '../reporter.dart';
+
+/// A reporter that only prints when a test fails.
+class FailuresOnlyReporter implements Reporter {
+ /// Whether the reporter should emit terminal color escapes.
+ final bool _color;
+
+ /// The terminal escape for green text, or the empty string if this is Windows
+ /// or not outputting to a terminal.
+ final String _green;
+
+ /// The terminal escape for red text, or the empty string if this is Windows
+ /// or not outputting to a terminal.
+ final String _red;
+
+ /// The terminal escape for yellow text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _yellow;
+
+ /// The terminal escape for gray text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _gray;
+
+ /// The terminal escape for bold text, or the empty string if this is
+ /// Windows or not outputting to a terminal.
+ final String _bold;
+
+ /// The terminal escape for removing test coloring, or the empty string if
+ /// this is Windows or not outputting to a terminal.
+ final String _noColor;
+
+ /// The engine used to run the tests.
+ final Engine _engine;
+
+ /// Whether the path to each test's suite should be printed.
+ final bool _printPath;
+
+ /// Whether the platform each test is running on should be printed.
+ final bool _printPlatform;
+
+ /// The size of `_engine.passed` last time a progress notification was
+ /// printed.
+ int _lastProgressPassed = 0;
+
+ /// The size of `_engine.skipped` last time a progress notification was
+ /// printed.
+ int _lastProgressSkipped = 0;
+
+ /// The size of `_engine.failed` last time a progress notification was
+ /// printed.
+ int _lastProgressFailed = 0;
+
+ /// The message printed for the last progress notification.
+ String _lastProgressMessage = '';
+
+ /// The suffix added to the last progress notification.
+ String? _lastProgressSuffix;
+
+ /// Whether the reporter is paused.
+ var _paused = false;
+
+ // Whether a notice should be logged about enabling stack trace chaining at
+ // the end of all tests running.
+ var _shouldPrintStackTraceChainingNotice = false;
+
+ /// The set of all subscriptions to various streams.
+ final _subscriptions = <StreamSubscription>{};
+
+ final StringSink _sink;
+
+ /// Watches the tests run by [engine] and prints their results to the
+ /// terminal.
+ ///
+ /// If [color] is `true`, this will use terminal colors; if it's `false`, it
+ /// won't. If [printPath] is `true`, this will print the path name as part of
+ /// the test description. Likewise, if [printPlatform] is `true`, this will
+ /// print the platform as part of the test description.
+ static FailuresOnlyReporter watch(Engine engine, StringSink sink,
+ {required bool color,
+ required bool printPath,
+ required bool printPlatform}) =>
+ FailuresOnlyReporter._(engine, sink,
+ color: color, printPath: printPath, printPlatform: printPlatform);
+
+ FailuresOnlyReporter._(this._engine, this._sink,
+ {required bool color,
+ required bool printPath,
+ required bool printPlatform})
+ : _printPath = printPath,
+ _printPlatform = printPlatform,
+ _color = color,
+ _green = color ? '\u001b[32m' : '',
+ _red = color ? '\u001b[31m' : '',
+ _yellow = color ? '\u001b[33m' : '',
+ _gray = color ? '\u001b[90m' : '',
+ _bold = color ? '\u001b[1m' : '',
+ _noColor = color ? '\u001b[0m' : '' {
+ _subscriptions.add(_engine.onTestStarted.listen(_onTestStarted));
+
+ // Convert the future to a stream so that the subscription can be paused or
+ // canceled.
+ _subscriptions.add(_engine.success.asStream().listen(_onDone));
+ }
+
+ @override
+ void pause() {
+ if (_paused) return;
+ _paused = true;
+
+ for (var subscription in _subscriptions) {
+ subscription.pause();
+ }
+ }
+
+ @override
+ void resume() {
+ if (!_paused) return;
+
+ for (var subscription in _subscriptions) {
+ subscription.resume();
+ }
+ }
+
+ void _cancel() {
+ for (var subscription in _subscriptions) {
+ subscription.cancel();
+ }
+ _subscriptions.clear();
+ }
+
+ /// A callback called when the engine begins running [liveTest].
+ void _onTestStarted(LiveTest liveTest) {
+ _subscriptions.add(liveTest.onError
+ .listen((error) => _onError(liveTest, error.error, error.stackTrace)));
+
+ _subscriptions.add(liveTest.onMessage.listen((message) {
+ // TODO - Should this suppress output? Behave like printOnFailure?
+ _progressLine(_description(liveTest));
+ var text = message.text;
+ if (message.type == MessageType.skip) text = ' $_yellow$text$_noColor';
+ _sink.writeln(text);
+ }));
+ }
+
+ /// A callback called when [liveTest] throws [error].
+ void _onError(LiveTest liveTest, Object error, StackTrace stackTrace) {
+ if (!liveTest.test.metadata.chainStackTraces &&
+ !liveTest.suite.isLoadSuite) {
+ _shouldPrintStackTraceChainingNotice = true;
+ }
+
+ if (liveTest.state.status != Status.complete) return;
+
+ _progressLine(_description(liveTest), suffix: ' $_bold$_red[E]$_noColor');
+
+ if (error is! LoadException) {
+ _sink
+ ..writeln(indent('$error'))
+ ..writeln(indent('$stackTrace'));
+ return;
+ }
+
+ // TODO - what type is this?
+ _sink.writeln(indent(error.toString(color: _color)));
+
+ // Only print stack traces for load errors that come from the user's code.
+ if (error.innerError is! FormatException && error.innerError is! String) {
+ _sink.writeln(indent('$stackTrace'));
+ }
+ }
+
+ /// A callback called when the engine is finished running tests.
+ ///
+ /// [success] will be `true` if all tests passed, `false` if some tests
+ /// failed, and `null` if the engine was closed prematurely.
+ void _onDone(bool? success) {
+ _cancel();
+ // A null success value indicates that the engine was closed before the
+ // tests finished running, probably because of a signal from the user, in
+ // which case we shouldn't print summary information.
+ if (success == null) return;
+
+ if (_engine.liveTests.isEmpty) {
+ _sink.writeln('No tests ran.');
+ } else if (!success) {
+ for (var liveTest in _engine.active) {
+ _progressLine(_description(liveTest),
+ suffix: ' - did not complete $_bold$_red[E]$_noColor');
+ }
+ _progressLine('Some tests failed.', color: _red);
+ } else if (_engine.passed.isEmpty) {
+ _progressLine('All tests skipped.');
+ } else {
+ _progressLine('All tests passed!');
+ }
+
+ if (_shouldPrintStackTraceChainingNotice) {
+ _sink
+ ..writeln('')
+ ..writeln('Consider enabling the flag chain-stack-traces to '
+ 'receive more detailed exceptions.\n'
+ "For example, 'dart test --chain-stack-traces'.");
+ }
+ }
+
+ /// Prints a line representing the current state of the tests.
+ ///
+ /// [message] goes after the progress report. If [color] is passed, it's used
+ /// as the color for [message]. If [suffix] is passed, it's added to the end
+ /// of [message].
+ void _progressLine(String message, {String? color, String? suffix}) {
+ // Print nothing if nothing has changed since the last progress line.
+ if (_engine.passed.length == _lastProgressPassed &&
+ _engine.skipped.length == _lastProgressSkipped &&
+ _engine.failed.length == _lastProgressFailed &&
+ message == _lastProgressMessage &&
+ // Don't re-print just because a suffix was removed.
+ (suffix == null || suffix == _lastProgressSuffix)) {
+ return;
+ }
+
+ _lastProgressPassed = _engine.passed.length;
+ _lastProgressSkipped = _engine.skipped.length;
+ _lastProgressFailed = _engine.failed.length;
+ _lastProgressMessage = message;
+ _lastProgressSuffix = suffix;
+
+ if (suffix != null) message += suffix;
+ color ??= '';
+ var buffer = StringBuffer();
+
+ buffer.write(_green);
+ buffer.write('+');
+ buffer.write(_engine.passed.length);
+ buffer.write(_noColor);
+
+ if (_engine.skipped.isNotEmpty) {
+ buffer.write(_yellow);
+ buffer.write(' ~');
+ buffer.write(_engine.skipped.length);
+ buffer.write(_noColor);
+ }
+
+ if (_engine.failed.isNotEmpty) {
+ buffer.write(_red);
+ buffer.write(' -');
+ buffer.write(_engine.failed.length);
+ buffer.write(_noColor);
+ }
+
+ buffer.write(': ');
+ buffer.write(color);
+ buffer.write(message);
+ buffer.write(_noColor);
+
+ _sink.writeln(buffer.toString());
+ }
+
+ /// Returns a description of [liveTest].
+ ///
+ /// This differs from the test's own description in that it may also include
+ /// the suite's name.
+ String _description(LiveTest liveTest) {
+ var name = liveTest.test.name;
+
+ if (_printPath &&
+ liveTest.suite is! LoadSuite &&
+ liveTest.suite.path != null) {
+ name = '${liveTest.suite.path}: $name';
+ }
+
+ if (_printPlatform) {
+ name = '[${liveTest.suite.platform.runtime.name}, '
+ '${liveTest.suite.platform.compiler.name}] $name';
+ }
+
+ if (liveTest.suite is LoadSuite) name = '$_bold$_gray$name$_noColor';
+
+ return name;
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/reporter/github.dart b/pkgs/test_core/lib/src/runner/reporter/github.dart
new file mode 100644
index 0000000..5249817
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/reporter/github.dart
@@ -0,0 +1,239 @@
+// Copyright (c) 2022, 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.
+
+// ignore_for_file: implementation_imports
+
+import 'dart:async';
+
+import 'package:test_api/src/backend/live_test.dart';
+import 'package:test_api/src/backend/message.dart';
+import 'package:test_api/src/backend/state.dart';
+import 'package:test_api/src/backend/util/pretty_print.dart';
+
+import '../engine.dart';
+import '../load_suite.dart';
+import '../reporter.dart';
+
+/// A reporter that prints test output using formatting for Github Actions.
+///
+/// See
+/// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
+/// for a description of the output format, and
+/// https://github.com/dart-lang/test/issues/1415 for discussions about this
+/// implementation.
+class GithubReporter implements Reporter {
+ /// The engine used to run the tests.
+ final Engine _engine;
+
+ /// Whether the path to each test's suite should be printed.
+ final bool _printPath;
+
+ /// Whether the platform each test is running on should be printed.
+ final bool _printPlatform;
+
+ /// Whether the reporter is paused.
+ var _paused = false;
+
+ /// The set of all subscriptions to various streams.
+ final _subscriptions = <StreamSubscription>{};
+
+ final StringSink _sink;
+
+ final Map<LiveTest, List<Message>> _testMessages = {};
+
+ final Set<LiveTest> _completedTests = {};
+
+ /// Watches the tests run by [engine] and prints their results as JSON.
+ static GithubReporter watch(
+ Engine engine,
+ StringSink sink, {
+ required bool printPath,
+ required bool printPlatform,
+ }) =>
+ GithubReporter._(engine, sink, printPath, printPlatform);
+
+ GithubReporter._(
+ this._engine, this._sink, this._printPath, this._printPlatform) {
+ _subscriptions.add(_engine.onTestStarted.listen(_onTestStarted));
+ _subscriptions.add(_engine.success.asStream().listen(_onDone));
+
+ // Add a spacer between pre-test output and the test results.
+ _sink.writeln();
+ }
+
+ @override
+ void pause() {
+ if (_paused) return;
+ _paused = true;
+
+ for (var subscription in _subscriptions) {
+ subscription.pause();
+ }
+ }
+
+ @override
+ void resume() {
+ if (!_paused) return;
+ _paused = false;
+
+ for (var subscription in _subscriptions) {
+ subscription.resume();
+ }
+ }
+
+ void _cancel() {
+ for (var subscription in _subscriptions) {
+ subscription.cancel();
+ }
+ _subscriptions.clear();
+ }
+
+ /// A callback called when the engine begins running [liveTest].
+ void _onTestStarted(LiveTest liveTest) {
+ // Convert the future to a stream so that the subscription can be paused or
+ // canceled.
+ _subscriptions.add(
+ liveTest.onComplete.asStream().listen((_) => _onComplete(liveTest)));
+
+ _subscriptions.add(liveTest.onError
+ .listen((error) => _onError(liveTest, error.error, error.stackTrace)));
+
+ // Collect messages from tests as they are emitted.
+ _subscriptions.add(liveTest.onMessage.listen((message) {
+ if (_completedTests.contains(liveTest)) {
+ // The test has already completed and it's previous messages were
+ // written out; ensure this post-completion output is not lost.
+ _sink.writeln(message.text);
+ } else {
+ _testMessages.putIfAbsent(liveTest, () => []).add(message);
+ }
+ }));
+ }
+
+ /// A callback called when [liveTest] finishes running.
+ void _onComplete(LiveTest test) {
+ final errors = test.errors;
+ final messages = _testMessages[test] ?? [];
+ final skipped = test.state.result == Result.skipped;
+ final failed = errors.isNotEmpty;
+ final loadSuite = test.suite is LoadSuite;
+ final synthetic = loadSuite ||
+ test.individualName == '(setUpAll)' ||
+ test.individualName == '(tearDownAll)';
+
+ // Mark this test as having completed.
+ _completedTests.add(test);
+
+ // Don't emit any info for loadSuite, setUpAll, or tearDownAll tests
+ // unless they contain errors or other info.
+ if (synthetic && (errors.isEmpty && messages.isEmpty)) {
+ return;
+ }
+
+ // For now, we use the same icon for both tests and test-like structures
+ // (loadSuite, setUpAll, tearDownAll).
+ var defaultIcon = synthetic ? _GithubMarkup.passed : _GithubMarkup.passed;
+ final prefix = failed
+ ? _GithubMarkup.failed
+ : skipped
+ ? _GithubMarkup.skipped
+ : defaultIcon;
+ final statusSuffix = failed
+ ? ' (failed)'
+ : skipped
+ ? ' (skipped)'
+ : '';
+
+ var name = test.test.name;
+ if (!loadSuite) {
+ if (_printPath && test.suite.path != null) {
+ name = '${test.suite.path}: $name';
+ }
+ }
+ if (_printPlatform) {
+ name = '[${test.suite.platform.runtime.name}, '
+ '${test.suite.platform.compiler.name}] $name';
+ }
+ if (messages.isEmpty && errors.isEmpty) {
+ _sink.writeln('$prefix $name$statusSuffix');
+ } else {
+ _sink.writeln(_GithubMarkup.startGroup('$prefix $name$statusSuffix'));
+ for (var message in messages) {
+ _sink.writeln(message.text);
+ }
+ for (var error in errors) {
+ _sink.writeln('${error.error}');
+ _sink.writeln(error.stackTrace.toString().trimRight());
+ }
+ _sink.writeln(_GithubMarkup.endGroup);
+ }
+ }
+
+ /// A callback called when [test] throws [error].
+ void _onError(LiveTest test, Object error, StackTrace stackTrace) {
+ if (_completedTests.contains(test)) {
+ final loadSuite = test.suite is LoadSuite;
+
+ final prefix = _GithubMarkup.failed;
+ final statusSuffix = ' (failed after test completion)';
+
+ var name = test.test.name;
+ if (!loadSuite) {
+ if (_printPath && test.suite.path != null) {
+ name = '${test.suite.path}: $name';
+ }
+ }
+ if (_printPlatform) {
+ name = '[${test.suite.platform.runtime.name}, '
+ '${test.suite.platform.compiler.name}] $name';
+ }
+
+ _sink.writeln(_GithubMarkup.startGroup('$prefix $name$statusSuffix'));
+ _sink.writeln('$error');
+ _sink.writeln(stackTrace.toString().trimRight());
+ _sink.writeln(_GithubMarkup.endGroup);
+ }
+ }
+
+ void _onDone(bool? success) {
+ _cancel();
+
+ _sink.writeln();
+
+ final hadFailures = _engine.failed.isNotEmpty;
+ final message = StringBuffer('${_engine.passed.length} '
+ '${pluralize('test', _engine.passed.length)} passed');
+ if (_engine.failed.isNotEmpty) {
+ message.write(', ${_engine.failed.length} failed');
+ }
+ if (_engine.skipped.isNotEmpty) {
+ message.write(', ${_engine.skipped.length} skipped');
+ }
+ message.write('.');
+ _sink.writeln(
+ hadFailures
+ ? _GithubMarkup.error(message.toString())
+ : '${_GithubMarkup.success} $message',
+ );
+ }
+}
+
+abstract class _GithubMarkup {
+ // Char sets avilable at https://www.compart.com/en/unicode/.
+ static const String passed = '✅';
+ static const String skipped = '❎';
+ static const String failed = '❌';
+ // The 'synthetic' icon is currently not used but is something to consider in
+ // order to draw a distinction between user tests and test-like supporting
+ // infrastructure.
+ // static const String synthetic = '⏺';
+ static const String success = '🎉';
+
+ static String startGroup(String title) =>
+ '::group::${title.replaceAll('\n', ' ')}';
+
+ static final String endGroup = '::endgroup::';
+
+ static String error(String message) => '::error::$message';
+}
diff --git a/pkgs/test_core/lib/src/runner/reporter/json.dart b/pkgs/test_core/lib/src/runner/reporter/json.dart
new file mode 100644
index 0000000..672f806
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/reporter/json.dart
@@ -0,0 +1,324 @@
+// Copyright (c) 2015, 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' show pid;
+
+import 'package:collection/collection.dart';
+import 'package:path/path.dart' as p;
+import 'package:stack_trace/stack_trace.dart';
+import 'package:test_api/hooks.dart' // ignore: implementation_imports
+ show
+ TestFailure;
+import 'package:test_api/src/backend/compiler.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
+
+import '../../platform.dart';
+import '../engine.dart';
+import '../load_suite.dart';
+import '../reporter.dart';
+import '../version.dart';
+
+/// A reporter that prints machine-readable JSON-formatted test results.
+class JsonReporter implements Reporter {
+ /// Whether the test runner will pause for debugging.
+ final bool _isDebugRun;
+
+ /// The engine used to run the tests.
+ final Engine _engine;
+
+ /// A stopwatch that tracks the duration of the full run.
+ final _stopwatch = Stopwatch();
+
+ /// Whether we've started [_stopwatch].
+ ///
+ /// We can't just use `_stopwatch.isRunning` because the stopwatch is stopped
+ /// when the reporter is paused.
+ var _stopwatchStarted = false;
+
+ /// An expando that associates unique IDs with [LiveTest]s.
+ final _liveTestIDs = <LiveTest, int>{};
+
+ /// An expando that associates unique IDs with [Suite]s.
+ final _suiteIDs = <Suite, int>{};
+
+ /// An expando that associates unique IDs with [Group]s.
+ final _groupIDs = <Group, int>{};
+
+ /// The next ID to associate with a [LiveTest].
+ var _nextID = 0;
+
+ /// Whether the reporter is paused.
+ var _paused = false;
+
+ /// The set of all subscriptions to various streams.
+ final _subscriptions = <StreamSubscription>{};
+
+ final StringSink _sink;
+
+ /// Watches the tests run by [engine] and prints their results as JSON.
+ static JsonReporter watch(Engine engine, StringSink sink,
+ {required bool isDebugRun}) =>
+ JsonReporter._(engine, sink, isDebugRun);
+
+ JsonReporter._(this._engine, this._sink, this._isDebugRun) {
+ _subscriptions.add(_engine.onTestStarted.listen(_onTestStarted));
+
+ // Convert the future to a stream so that the subscription can be paused or
+ // canceled.
+ _subscriptions.add(_engine.success.asStream().listen(_onDone));
+
+ _subscriptions.add(_engine.onSuiteAdded.listen(null, onDone: () {
+ _emit('allSuites', {
+ 'count': _engine.addedSuites.length,
+ 'time': _stopwatch.elapsed.inMilliseconds
+ });
+ }));
+
+ _emit('start',
+ {'protocolVersion': '0.1.1', 'runnerVersion': testVersion, 'pid': pid});
+ }
+
+ @override
+ void pause() {
+ if (_paused) return;
+ _paused = true;
+
+ _stopwatch.stop();
+
+ for (var subscription in _subscriptions) {
+ subscription.pause();
+ }
+ }
+
+ @override
+ void resume() {
+ if (!_paused) return;
+ _paused = false;
+
+ if (_stopwatchStarted) _stopwatch.start();
+
+ for (var subscription in _subscriptions) {
+ subscription.resume();
+ }
+ }
+
+ void _cancel() {
+ for (var subscription in _subscriptions) {
+ subscription.cancel();
+ }
+ _subscriptions.clear();
+ }
+
+ /// A callback called when the engine begins running [liveTest].
+ void _onTestStarted(LiveTest liveTest) {
+ if (!_stopwatchStarted) {
+ _stopwatchStarted = true;
+ _stopwatch.start();
+ }
+
+ var suiteID = _idForSuite(liveTest.suite);
+
+ // Don't emit groups for load suites. They're always empty and they provide
+ // unnecessary clutter.
+ var groupIDs = liveTest.suite is LoadSuite
+ ? <int>[]
+ : _idsForGroups(liveTest.groups, liveTest.suite);
+
+ var suiteConfig = _configFor(liveTest.suite);
+ var id = _nextID++;
+ _liveTestIDs[liveTest] = id;
+ _emit('testStart', {
+ 'test': {
+ 'id': id,
+ 'name': liveTest.test.name,
+ 'suiteID': suiteID,
+ 'groupIDs': groupIDs,
+ 'metadata': _serializeMetadata(suiteConfig, liveTest.test.metadata),
+ ..._frameInfo(suiteConfig, liveTest.test.trace, liveTest.suite.platform,
+ liveTest.suite.path!),
+ }
+ });
+
+ // Convert the future to a stream so that the subscription can be paused or
+ // canceled.
+ _subscriptions.add(
+ liveTest.onComplete.asStream().listen((_) => _onComplete(liveTest)));
+
+ _subscriptions.add(liveTest.onError
+ .listen((error) => _onError(liveTest, error.error, error.stackTrace)));
+
+ _subscriptions.add(liveTest.onMessage.listen((message) {
+ _emit('print', {
+ 'testID': id,
+ 'messageType': message.type.name,
+ 'message': message.text
+ });
+ }));
+ }
+
+ /// Returns an ID for [suite].
+ ///
+ /// If [suite] doesn't have an ID yet, this assigns one and emits a new event
+ /// for that suite.
+ int _idForSuite(Suite suite) {
+ if (_suiteIDs.containsKey(suite)) return _suiteIDs[suite]!;
+
+ var id = _nextID++;
+ _suiteIDs[suite] = id;
+
+ // Give the load suite's suite the same ID, because it doesn't have any
+ // different metadata.
+ if (suite is LoadSuite) {
+ suite.suite.then((runnerSuite) {
+ if (runnerSuite == null) return;
+ _suiteIDs[runnerSuite] = id;
+ if (!_isDebugRun) return;
+
+ // TODO(nweiz): test this when we have a library for communicating with
+ // the Chrome remote debugger, or when we have VM debug support.
+ _emit('debug', {
+ 'suiteID': id,
+ 'observatory': runnerSuite.environment.observatoryUrl?.toString(),
+ 'remoteDebugger':
+ runnerSuite.environment.remoteDebuggerUrl?.toString(),
+ });
+ });
+ }
+
+ _emit('suite', {
+ 'suite': <String, Object?>{
+ 'id': id,
+ 'platform': suite.platform.runtime.identifier,
+ 'path': suite.path
+ }
+ });
+ return id;
+ }
+
+ /// Returns a list of the IDs for all the groups in [groups], which are
+ /// contained in the suite identified by [suiteID].
+ ///
+ /// If a group doesn't have an ID yet, this assigns one and emits a new event
+ /// for that group.
+ List<int> _idsForGroups(Iterable<Group> groups, Suite suite) {
+ int? parentID;
+ return groups.map((group) {
+ if (_groupIDs.containsKey(group)) {
+ return parentID = _groupIDs[group]!;
+ }
+
+ var id = _nextID++;
+ _groupIDs[group] = id;
+
+ var suiteConfig = _configFor(suite);
+ _emit('group', {
+ 'group': {
+ 'id': id,
+ 'suiteID': _idForSuite(suite),
+ 'parentID': parentID,
+ 'name': group.name,
+ 'metadata': _serializeMetadata(suiteConfig, group.metadata),
+ 'testCount': group.testCount,
+ ..._frameInfo(suiteConfig, group.trace, suite.platform, suite.path!)
+ }
+ });
+ parentID = id;
+ return id;
+ }).toList();
+ }
+
+ /// Serializes [metadata] into a JSON-protocol-compatible map.
+ Map _serializeMetadata(SuiteConfiguration suiteConfig, Metadata metadata) =>
+ suiteConfig.runSkipped
+ ? {'skip': false, 'skipReason': null}
+ : {'skip': metadata.skip, 'skipReason': metadata.skipReason};
+
+ /// A callback called when [liveTest] finishes running.
+ void _onComplete(LiveTest liveTest) {
+ _emit('testDone', {
+ 'testID': _liveTestIDs[liveTest],
+ 'result': _normalizeTestResult(liveTest),
+ 'skipped': liveTest.state.result == Result.skipped,
+ 'hidden': !_engine.liveTests.contains(liveTest)
+ });
+ }
+
+ String _normalizeTestResult(LiveTest liveTest) {
+ // For backwards-compatibility, report skipped tests as successes.
+ if (liveTest.state.result == Result.skipped) return 'success';
+ // if test is still active, it was probably cancelled
+ if (_engine.active.contains(liveTest)) return 'error';
+ return liveTest.state.result.toString();
+ }
+
+ /// A callback called when [liveTest] throws [error].
+ void _onError(LiveTest liveTest, Object error, StackTrace stackTrace) {
+ _emit('error', {
+ 'testID': _liveTestIDs[liveTest],
+ 'error': error.toString(),
+ 'stackTrace': '$stackTrace',
+ 'isFailure': error is TestFailure
+ });
+ }
+
+ /// A callback called when the engine is finished running tests.
+ ///
+ /// [success] will be `true` if all tests passed, `false` if some tests
+ /// failed, and `null` if the engine was closed prematurely.
+ void _onDone(bool? success) {
+ _cancel();
+ _stopwatch.stop();
+
+ _emit('done', {'success': success});
+ }
+
+ /// Returns the configuration for [suite].
+ ///
+ /// If [suite] is a [RunnerSuite], this returns [RunnerSuite.config].
+ /// Otherwise, it returns [SuiteConfiguration.empty].
+ SuiteConfiguration _configFor(Suite suite) =>
+ suite is RunnerSuite ? suite.config : SuiteConfiguration.empty;
+
+ /// Emits an event with the given type and attributes.
+ void _emit(String type, Map attributes) {
+ attributes['type'] = type;
+ attributes['time'] = _stopwatch.elapsed.inMilliseconds;
+ _sink.writeln(jsonEncode(attributes));
+ }
+
+ /// Returns a map with the line, column, and URL information for the first
+ /// frame of [trace], as well as the first line in the original file.
+ ///
+ /// If javascript traces are enabled and the test is on a javascript platform,
+ /// or if the [trace] is null or empty, then the line, column, and url will
+ /// all be `null`.
+ Map<String, dynamic> _frameInfo(SuiteConfiguration suiteConfig, Trace? trace,
+ SuitePlatform platform, String suitePath) {
+ var absoluteSuitePath = p.canonicalize(p.absolute(suitePath));
+ var frame = trace?.frames.first;
+ if (frame == null || (suiteConfig.jsTrace && platform.compiler.isJS)) {
+ return {'line': null, 'column': null, 'url': null};
+ }
+
+ var rootFrame = trace?.frames.firstWhereOrNull((frame) =>
+ frame.uri.scheme == 'file' &&
+ p.canonicalize(frame.uri.toFilePath()) == absoluteSuitePath);
+ return {
+ 'line': frame.line,
+ 'column': frame.column,
+ 'url': frame.uri.toString(),
+ if (rootFrame != null && rootFrame != frame) ...{
+ 'root_line': rootFrame.line,
+ 'root_column': rootFrame.column,
+ 'root_url': rootFrame.uri.toString(),
+ }
+ };
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/reporter/multiplex.dart b/pkgs/test_core/lib/src/runner/reporter/multiplex.dart
new file mode 100644
index 0000000..59a9841
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/reporter/multiplex.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, 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 '../reporter.dart';
+
+class MultiplexReporter implements Reporter {
+ Iterable<Reporter> delegates;
+
+ MultiplexReporter(this.delegates);
+
+ @override
+ void pause() {
+ for (var d in delegates) {
+ d.pause();
+ }
+ }
+
+ @override
+ void resume() {
+ for (var d in delegates) {
+ d.resume();
+ }
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/runner_suite.dart b/pkgs/test_core/lib/src/runner/runner_suite.dart
new file mode 100644
index 0000000..dcceb6b
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/runner_suite.dart
@@ -0,0 +1,175 @@
+// Copyright (c) 2015, 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 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
+
+import 'environment.dart';
+import 'suite.dart';
+
+/// A suite produced and consumed by the test runner that has runner-specific
+/// logic and lifecycle management.
+///
+/// This is separated from [Suite] because the backend library (which will
+/// eventually become its own package) is primarily for test code itself to use,
+/// for which the [RunnerSuite] APIs don't make sense.
+///
+/// A [RunnerSuite] can be produced and controlled using a
+/// [RunnerSuiteController].
+class RunnerSuite extends Suite {
+ final RunnerSuiteController _controller;
+
+ /// The environment in which this suite runs.
+ Environment get environment => _controller._environment;
+
+ /// The configuration for this suite.
+ SuiteConfiguration get config => _controller._config;
+
+ /// Whether the suite is paused for debugging.
+ ///
+ /// When using a dev inspector, this may also mean that the entire browser is
+ /// paused.
+ bool get isDebugging => _controller._isDebugging;
+
+ /// A broadcast stream that emits an event whenever the suite is paused for
+ /// debugging or resumed afterwards.
+ ///
+ /// The event is `true` when debugging starts and `false` when it ends.
+ Stream<bool> get onDebugging => _controller._onDebuggingController.stream;
+
+ /// A shortcut constructor for creating a [RunnerSuite] that never goes into
+ /// debugging mode and doesn't support suite channels.
+ factory RunnerSuite(Environment environment, SuiteConfiguration config,
+ Group group, SuitePlatform platform,
+ {String? path, void Function()? onClose}) {
+ var controller =
+ RunnerSuiteController._local(environment, config, onClose: onClose);
+ var suite = RunnerSuite._(controller, group, platform, path: path);
+ controller._suite = Future.value(suite);
+ return suite;
+ }
+
+ RunnerSuite._(this._controller, Group group, SuitePlatform platform,
+ {String? path})
+ : super(group, platform,
+ path: path, ignoreTimeouts: _controller._config.ignoreTimeouts);
+
+ @override
+ RunnerSuite filter(bool Function(Test) callback) {
+ var filtered = group.filter(callback);
+ filtered ??= Group.root([], metadata: metadata);
+ return RunnerSuite._(_controller, filtered, platform, path: path);
+ }
+
+ /// Closes the suite and releases any resources associated with it.
+ Future close() => _controller._close();
+
+ /// Collects a hit-map containing merged coverage.
+ ///
+ /// Result is suitable for input to the coverage formatters provided by
+ /// `package:coverage`.
+ Future<Map<String, dynamic>> gatherCoverage() async =>
+ (await _controller._gatherCoverage?.call()) ?? {};
+}
+
+/// A class that exposes and controls a [RunnerSuite].
+class RunnerSuiteController {
+ /// The suite controlled by this controller.
+ Future<RunnerSuite> get suite => _suite;
+ late final Future<RunnerSuite> _suite;
+
+ /// The backing value for [suite.environment].
+ final Environment _environment;
+
+ /// The configuration for this suite.
+ final SuiteConfiguration _config;
+
+ /// A channel that communicates with the remote suite.
+ final MultiChannel? _suiteChannel;
+
+ /// The function to call when the suite is closed.
+ final FutureOr<void> Function()? _onClose;
+
+ /// The backing value for [suite.isDebugging].
+ bool _isDebugging = false;
+
+ /// The controller for [suite.onDebugging].
+ final _onDebuggingController = StreamController<bool>.broadcast();
+
+ /// The channel names that have already been used.
+ final _channelNames = <String>{};
+
+ /// Collects a hit-map containing merged coverage.
+ final Future<Map<String, dynamic>> Function()? _gatherCoverage;
+
+ RunnerSuiteController(this._environment, this._config, this._suiteChannel,
+ Future<Group> groupFuture, SuitePlatform platform,
+ {String? path,
+ void Function()? onClose,
+ Future<Map<String, dynamic>> Function()? gatherCoverage})
+ : _onClose = onClose,
+ _gatherCoverage = gatherCoverage {
+ _suite = groupFuture
+ .then((group) => RunnerSuite._(this, group, platform, path: path));
+ }
+
+ /// Used by [RunnerSuite.new] to create a runner suite that's not loaded from
+ /// an external source.
+ RunnerSuiteController._local(this._environment, this._config,
+ {void Function()? onClose,
+ Future<Map<String, dynamic>> Function()? gatherCoverage})
+ : _suiteChannel = null,
+ _onClose = onClose,
+ _gatherCoverage = gatherCoverage;
+
+ /// Sets whether the suite is paused for debugging.
+ ///
+ /// If this is different than [suite.isDebugging], this will automatically
+ /// send out an event along [suite.onDebugging].
+ void setDebugging(bool debugging) {
+ if (debugging == _isDebugging) return;
+ _isDebugging = debugging;
+ _onDebuggingController.add(debugging);
+ }
+
+ /// Returns a channel that communicates with the remote suite.
+ ///
+ /// This connects to a channel created by code in the test worker calling the
+ /// `suiteChannel` argument from a `beforeLoad` callback to `serializeSuite`
+ /// with the same name.
+ /// It can be used used to send and receive any JSON-serializable object.
+ ///
+ /// This is exposed on the [RunnerSuiteController] so that runner plugins can
+ /// communicate with the workers they spawn before the associated [suite] is
+ /// fully loaded.
+ StreamChannel channel(String name) {
+ if (!_channelNames.add(name)) {
+ throw StateError('Duplicate RunnerSuite.channel() connection "$name".');
+ }
+
+ var suiteChannel = _suiteChannel;
+ if (suiteChannel == null) {
+ throw StateError('No suite channel set up but one was requested.');
+ }
+
+ var channel = suiteChannel.virtualChannel();
+ suiteChannel.sink
+ .add({'type': 'suiteChannel', 'name': name, 'id': channel.id});
+ return channel;
+ }
+
+ /// The backing function for [suite.close].
+ Future _close() => _closeMemo.runOnce(() async {
+ await _onDebuggingController.close();
+ var onClose = _onClose;
+ if (onClose != null) await onClose();
+ });
+ final _closeMemo = AsyncMemoizer<void>();
+}
diff --git a/pkgs/test_core/lib/src/runner/runner_test.dart b/pkgs/test_core/lib/src/runner/runner_test.dart
new file mode 100644
index 0000000..2823983
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/runner_test.dart
@@ -0,0 +1,108 @@
+// Copyright (c) 2015, 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 'package:stack_trace/stack_trace.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart'
+ show Metadata, RemoteException, SuitePlatform;
+import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/live_test_controller.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/message.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
+
+import 'spawn_hybrid.dart';
+
+/// A test running remotely, controlled by a stream channel.
+class RunnerTest extends Test {
+ @override
+ final String name;
+ @override
+ final Metadata metadata;
+ @override
+ final Trace? trace;
+
+ /// The channel used to communicate with the test's `RemoteListener`.
+ final MultiChannel _channel;
+
+ RunnerTest(this.name, this.metadata, this.trace, this._channel);
+
+ @override
+ LiveTest load(Suite suite, {Iterable<Group>? groups}) {
+ late final LiveTestController controller;
+ late final VirtualChannel testChannel;
+ controller = LiveTestController(suite, this, () {
+ controller.setState(const State(Status.running, Result.success));
+
+ testChannel = _channel.virtualChannel();
+ _channel.sink.add({'command': 'run', 'channel': testChannel.id});
+
+ testChannel.stream.listen((message) {
+ final msg = message as Map;
+ switch (msg['type'] as String) {
+ case 'error':
+ var asyncError = RemoteException.deserialize(
+ msg['error'] as Map<String, dynamic>);
+ var stackTrace = asyncError.stackTrace;
+ controller.addError(asyncError.error, stackTrace);
+ break;
+
+ case 'state-change':
+ controller.setState(State(Status.parse(msg['status'] as String),
+ Result.parse(msg['result'] as String)));
+ break;
+
+ case 'message':
+ controller.message(Message(
+ MessageType.parse(msg['message-type'] as String),
+ msg['text'] as String));
+ break;
+
+ case 'complete':
+ controller.completer.complete();
+ break;
+
+ case 'spawn-hybrid-uri':
+ // When we kill the isolate that the test lives in, that will close
+ // this virtual channel and cause the spawned isolate to close as
+ // well.
+ spawnHybridUri(msg['url'] as String, msg['message'], suite).pipe(
+ testChannel.virtualChannel((msg['channel'] as num).toInt()));
+ break;
+ }
+ }, onDone: () {
+ // When the test channel closes—presumably because the browser
+ // closed—mark the test as complete no matter what.
+ if (controller.completer.isCompleted) return;
+ controller.completer.complete();
+ });
+ }, () {
+ // If the test has finished running, just disconnect the channel.
+ if (controller.completer.isCompleted) {
+ testChannel.sink.close();
+ return;
+ }
+
+ unawaited(() async {
+ // If the test is still running, send it a message telling it to shut
+ // down ASAP. This causes the [Invoker] to eagerly throw exceptions
+ // whenever the test touches it.
+ testChannel.sink.add({'command': 'close'});
+ await controller.completer.future;
+ await testChannel.sink.close();
+ }());
+ }, groups: groups);
+ return controller;
+ }
+
+ @override
+ Test? forPlatform(SuitePlatform platform) {
+ if (!metadata.testOn.evaluate(platform)) return null;
+ return RunnerTest(name, metadata.forPlatform(platform), trace, _channel);
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/runtime_selection.dart b/pkgs/test_core/lib/src/runner/runtime_selection.dart
new file mode 100644
index 0000000..7f64a5c
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/runtime_selection.dart
@@ -0,0 +1,25 @@
+// 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.
+
+import 'package:source_span/source_span.dart';
+
+/// A runtime on which the user has chosen to run tests.
+class RuntimeSelection {
+ /// The name of the runtime.
+ final String name;
+
+ /// The location in the configuration file of this runtime string, or `null`
+ /// if it was defined outside a configuration file (for example, on the
+ /// command line).
+ final SourceSpan? span;
+
+ RuntimeSelection(this.name, [this.span]);
+
+ @override
+ bool operator ==(Object other) =>
+ other is RuntimeSelection && other.name == name;
+
+ @override
+ int get hashCode => name.hashCode;
+}
diff --git a/pkgs/test_core/lib/src/runner/spawn_hybrid.dart b/pkgs/test_core/lib/src/runner/spawn_hybrid.dart
new file mode 100644
index 0000000..ed236b0
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/spawn_hybrid.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2016, 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:io';
+import 'dart:isolate';
+
+import 'package:analyzer/dart/analysis/utilities.dart';
+import 'package:async/async.dart';
+import 'package:path/path.dart' as p;
+import 'package:stream_channel/isolate_channel.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart' show RemoteException;
+import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
+
+import '../util/dart.dart' as dart;
+import '../util/package_config.dart';
+import 'package_version.dart';
+
+/// Spawns a hybrid isolate from [url] with the given [message], and returns a
+/// [StreamChannel] that communicates with it.
+///
+/// This connects the main isolate to the hybrid isolate, whereas
+/// `lib/src/frontend/spawn_hybrid.dart` connects the test isolate to the main
+/// isolate.
+///
+/// If [uri] is relative, it will be interpreted relative to the `file:` URL
+/// for [suite]. If it's root-relative (that is, if it begins with `/`) it will
+/// be interpreted relative to the root of the package (the directory that
+/// contains `pubspec.yaml`, *not* the `test/` directory). If it's a `package:`
+/// URL, it will be resolved using the current package's dependency
+/// constellation.
+StreamChannel spawnHybridUri(String url, Object? message, Suite suite) {
+ return StreamChannelCompleter.fromFuture(() async {
+ url = await _normalizeUrl(url, suite);
+ var port = ReceivePort();
+ var onExitPort = ReceivePort();
+ try {
+ var code = '''
+ ${await _languageVersionCommentFor(url)}
+
+ import "package:test_core/src/runner/hybrid_listener.dart";
+
+ import "${url.replaceAll(r'$', '%24')}" as lib;
+
+ void main(_, List data) => listen(() => lib.hybridMain, data);
+ ''';
+
+ var isolate = await dart.runInIsolate(code, [port.sendPort, message],
+ onExit: onExitPort.sendPort);
+
+ // Ensure that we close [port] and [channel] when the isolate exits.
+ var disconnector = Disconnector<void>();
+ onExitPort.listen((_) {
+ disconnector.disconnect();
+ port.close();
+ onExitPort.close();
+ });
+
+ return IsolateChannel<Object?>.connectReceive(port)
+ .transform(disconnector)
+ .transformSink(StreamSinkTransformer.fromHandlers(handleDone: (sink) {
+ // If the user closes the stream channel, kill the isolate.
+ isolate.kill();
+ port.close();
+ onExitPort.close();
+ sink.close();
+ }));
+ } catch (error, stackTrace) {
+ port.close();
+ onExitPort.close();
+
+ // Make sure any errors in spawning the isolate are forwarded to the test.
+ return StreamChannel(
+ Stream.fromFuture(Future.value({
+ 'type': 'error',
+ 'error': RemoteException.serialize(error, stackTrace)
+ })),
+ NullStreamSink<void>());
+ }
+ }());
+}
+
+/// Normalizes [url] to an absolute url, resolving `package:` urls with the
+/// current package config.
+///
+/// If [url] has a scheme other than `package:`, then it is returned as is.
+///
+/// Follows the rules for relative/absolute paths outlined in [spawnHybridUri].
+Future<String> _normalizeUrl(String url, Suite suite) async {
+ final parsedUri = Uri.parse(url);
+
+ switch (parsedUri.scheme) {
+ case '':
+ var isRootRelative = parsedUri.path.startsWith('/');
+
+ if (isRootRelative) {
+ // We assume that the current path is the package root. `pub run`
+ // enforces this currently, but at some point it would probably be good
+ // to pass in an explicit root.
+ return p.url
+ .join(p.toUri(p.current).toString(), parsedUri.path.substring(1));
+ } else {
+ var suitePath = suite.path!;
+ return p.url.join(
+ p.url.dirname(p.toUri(p.absolute(suitePath)).toString()),
+ parsedUri.toString());
+ }
+ case 'package':
+ final resolvedUri = await Isolate.resolvePackageUri(parsedUri);
+ if (resolvedUri == null) {
+ throw ArgumentError.value(
+ url, 'uri', 'Could not resolve the package URI');
+ }
+ return resolvedUri.toString();
+ default:
+ return url;
+ }
+}
+
+/// Computes the a language version comment for the library at [uri].
+///
+/// If there is a language version comment in the file, that is returned.
+///
+/// If the URI has a `data` scheme, a comment representing the language version of
+/// the current package is returned.
+///
+/// Otherwise a comment representing the default version from the
+/// [currentPackageConfig] is returned.
+///
+/// If no default language version is known (the URI scheme is not recognized
+/// for instance), then an empty string is returned.
+Future<String> _languageVersionCommentFor(String url) async {
+ var parsedUri = Uri.parse(url);
+
+ // Returns the explicit language version comment if one exists.
+ var result = parseString(
+ content: await _readUri(parsedUri),
+ path: parsedUri.scheme == 'data' ? null : p.fromUri(parsedUri),
+ throwIfDiagnostics: false);
+ var languageVersionComment = result.unit.languageVersionToken?.value();
+ if (languageVersionComment != null) return languageVersionComment.toString();
+
+ // Returns the default language version for the package if one exists.
+ if (parsedUri.scheme.isEmpty || parsedUri.scheme == 'file') {
+ var packageConfig = await currentPackageConfig;
+ var package = packageConfig.packageOf(parsedUri);
+ var version = package?.languageVersion;
+ if (version != null) return '// @dart=$version';
+ }
+
+ // Returns the root package language version for `data` URIs. These are
+ // assumed to be from `spawnHybridCode` calls.
+ if (parsedUri.scheme == 'data') {
+ return await rootPackageLanguageVersionComment;
+ }
+
+ // Fall back on no language comment.
+ return '';
+}
+
+Future<String> _readUri(Uri uri) async => switch (uri.scheme) {
+ '' || 'file' => await File.fromUri(uri).readAsString(),
+ 'data' => uri.data!.contentAsString(),
+ _ => throw ArgumentError.value(uri, 'uri',
+ 'Only data and file uris (as well as relative paths) are supported'),
+ };
diff --git a/pkgs/test_core/lib/src/runner/suite.dart b/pkgs/test_core/lib/src/runner/suite.dart
new file mode 100644
index 0000000..b5ee0be
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/suite.dart
@@ -0,0 +1,498 @@
+// Copyright (c) 2016, 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 'package:boolean_selector/boolean_selector.dart';
+import 'package:collection/collection.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test_api/scaffolding.dart' show Timeout;
+import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/platform_selector.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+
+import 'compiler_selection.dart';
+import 'runtime_selection.dart';
+
+/// A filter on tests cases to run within a test suite.
+///
+/// The tests that match a test selection should match all available criteria. A
+/// suite run may include multiple test selections, and a test should run if it
+/// matches any test selection.
+///
+/// An empty [TestSelection()] will run all tests in the suite.
+final class TestSelection {
+ /// The patterns to check against test case names.
+ ///
+ /// Only run tests which match all the patterns.
+ final Set<Pattern> testPatterns;
+
+ /// Only run tests that originate from this line in the test suite.
+ final int? line;
+
+ /// Only run tests that originate from this column in the test suite.
+ final int? col;
+
+ const TestSelection({this.testPatterns = const {}, this.line, this.col});
+}
+
+/// Suite-level configuration.
+///
+/// This tracks configuration that can differ from suite to suite.
+final class SuiteConfiguration {
+ /// Empty configuration with only default values.
+ ///
+ /// Using this is slightly more efficient than manually constructing a new
+ /// configuration with no arguments.
+ static final empty = SuiteConfiguration._(
+ allowDuplicateTestNames: null,
+ allowTestRandomization: null,
+ jsTrace: null,
+ runSkipped: null,
+ dart2jsArgs: null,
+ testSelections: const {},
+ precompiledPath: null,
+ runtimes: null,
+ compilerSelections: null,
+ tags: null,
+ onPlatform: null,
+ metadata: null,
+ ignoreTimeouts: null);
+
+ /// Whether or not duplicate test (or group) names are allowed within the same
+ /// test suite.
+ //
+ // TODO: Change the default https://github.com/dart-lang/test/issues/1571
+ bool get allowDuplicateTestNames => _allowDuplicateTestNames ?? true;
+ final bool? _allowDuplicateTestNames;
+
+ /// Whether test randomization should be allowed for this test.
+ bool get allowTestRandomization => _allowTestRandomization ?? true;
+ final bool? _allowTestRandomization;
+
+ /// Whether JavaScript stack traces should be left as-is or converted to
+ /// Dart-like traces.
+ bool get jsTrace => _jsTrace ?? false;
+ final bool? _jsTrace;
+
+ /// Whether skipped tests should be run.
+ bool get runSkipped => _runSkipped ?? false;
+ final bool? _runSkipped;
+
+ /// The path to a mirror of this package containing HTML that points to
+ /// precompiled JS.
+ ///
+ /// This is used by the internal Google test runner so that test compilation
+ /// can more effectively make use of Google's build tools.
+ final String? precompiledPath;
+
+ /// Additional arguments to pass to dart2js.
+ ///
+ /// Note that this if multiple suites run the same JavaScript on different
+ /// runtimes, and they have different [dart2jsArgs], only one (undefined)
+ /// suite's arguments will be used.
+ final List<String> dart2jsArgs;
+
+ /// The selections for which tests to run in a suite.
+ ///
+ /// If empty, no tests have been selected for this suite.
+ /// Tests must be selected once for a suite before the suite can run.
+ /// When merging suite configurations, only one, or neither, should have tests
+ /// already selected.
+ ///
+ /// A test should run within a suite if it matches any selection in
+ /// [testSelections].
+ final Set<TestSelection> testSelections;
+
+ /// The set of compiler selections for running tests.
+ final List<CompilerSelection>? compilerSelections;
+
+ /// The set of runtimes on which to run tests.
+ List<String> get runtimes => _runtimes == null
+ ? const ['vm']
+ : List.unmodifiable(_runtimes.map((runtime) => runtime.name));
+ final List<RuntimeSelection>? _runtimes;
+
+ /// Configuration for particular tags.
+ ///
+ /// The keys are tag selectors, and the values are configurations for tests
+ /// whose tags match those selectors.
+ final Map<BooleanSelector, SuiteConfiguration> tags;
+
+ /// Configuration for particular platforms.
+ ///
+ /// The keys are platform selectors, and the values are configurations for
+ /// those platforms. These configuration should only contain test-level
+ /// configuration fields, but that isn't enforced.
+ final Map<PlatformSelector, SuiteConfiguration> onPlatform;
+
+ /// The global test metadata derived from this configuration.
+ Metadata get metadata {
+ if (tags.isEmpty && onPlatform.isEmpty) return _metadata;
+ return _metadata.change(
+ forTag: tags.map((key, config) => MapEntry(key, config.metadata)),
+ onPlatform:
+ onPlatform.map((key, config) => MapEntry(key, config.metadata)));
+ }
+
+ final Metadata _metadata;
+
+ /// The set of tags that have been declared in any way in this configuration.
+ late final Set<String> knownTags = UnmodifiableSetView({
+ ..._metadata.tags,
+ for (var selector in tags.keys) ...selector.variables,
+ for (var configuration in tags.values) ...configuration.knownTags,
+ for (var configuration in onPlatform.values) ...configuration.knownTags,
+ });
+
+ /// Whether or not timeouts should be ignored.
+ final bool? _ignoreTimeouts;
+ bool get ignoreTimeouts => _ignoreTimeouts ?? false;
+
+ factory SuiteConfiguration(
+ {required bool? allowDuplicateTestNames,
+ required bool? allowTestRandomization,
+ required bool? jsTrace,
+ required bool? runSkipped,
+ required Iterable<String>? dart2jsArgs,
+ required String? precompiledPath,
+ required Iterable<CompilerSelection>? compilerSelections,
+ required Iterable<RuntimeSelection>? runtimes,
+ required Map<BooleanSelector, SuiteConfiguration>? tags,
+ required Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ required bool? ignoreTimeouts,
+
+ // Test-level configuration
+ required Timeout? timeout,
+ required bool? verboseTrace,
+ required bool? chainStackTraces,
+ required bool? skip,
+ required int? retry,
+ required String? skipReason,
+ required PlatformSelector? testOn,
+ required Iterable<String>? addTags}) {
+ var config = SuiteConfiguration._(
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ allowTestRandomization: allowTestRandomization,
+ jsTrace: jsTrace,
+ runSkipped: runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ testSelections: const {},
+ precompiledPath: precompiledPath,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ tags: tags,
+ onPlatform: onPlatform,
+ ignoreTimeouts: ignoreTimeouts,
+ metadata: Metadata(
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ tags: addTags));
+ return config._resolveTags();
+ }
+
+ /// A constructor that doesn't require all of its options to be passed.
+ ///
+ /// This should only be used in situations where you really only want to
+ /// configure a specific restricted set of options.
+ factory SuiteConfiguration._unsafe(
+ {bool? allowDuplicateTestNames,
+ bool? allowTestRandomization,
+ bool? jsTrace,
+ bool? runSkipped,
+ Iterable<String>? dart2jsArgs,
+ String? precompiledPath,
+ Iterable<CompilerSelection>? compilerSelections,
+ Iterable<RuntimeSelection>? runtimes,
+ Map<BooleanSelector, SuiteConfiguration>? tags,
+ Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ bool? ignoreTimeouts,
+
+ // Test-level configuration
+ Timeout? timeout,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ bool? skip,
+ int? retry,
+ String? skipReason,
+ PlatformSelector? testOn,
+ Iterable<String>? addTags}) =>
+ SuiteConfiguration(
+ allowDuplicateTestNames: allowDuplicateTestNames,
+ allowTestRandomization: allowTestRandomization,
+ jsTrace: jsTrace,
+ runSkipped: runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ precompiledPath: precompiledPath,
+ compilerSelections: compilerSelections,
+ runtimes: runtimes,
+ tags: tags,
+ onPlatform: onPlatform,
+ ignoreTimeouts: ignoreTimeouts,
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ addTags: addTags);
+
+ /// A specialized constructor for only configuring the runtimes.
+ factory SuiteConfiguration.runtimes(Iterable<RuntimeSelection> runtimes) =>
+ SuiteConfiguration._unsafe(runtimes: runtimes);
+
+ /// A specialized constructor for only configuring runSkipped.
+ factory SuiteConfiguration.runSkipped(bool runSkipped) =>
+ SuiteConfiguration._unsafe(runSkipped: runSkipped);
+
+ /// A specialized constructor for only configuring the timeout.
+ factory SuiteConfiguration.timeout(Timeout timeout) =>
+ SuiteConfiguration._unsafe(timeout: timeout);
+
+ /// Creates new SuiteConfiguration.
+ ///
+ /// Unlike [SuiteConfiguration.new], this assumes [tags] is already
+ /// resolved.
+ SuiteConfiguration._({
+ required bool? allowDuplicateTestNames,
+ required bool? allowTestRandomization,
+ required bool? jsTrace,
+ required bool? runSkipped,
+ required Iterable<String>? dart2jsArgs,
+ required this.testSelections,
+ required this.precompiledPath,
+ required Iterable<CompilerSelection>? compilerSelections,
+ required Iterable<RuntimeSelection>? runtimes,
+ required Map<BooleanSelector, SuiteConfiguration>? tags,
+ required Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ required Metadata? metadata,
+ required bool? ignoreTimeouts,
+ }) : _allowDuplicateTestNames = allowDuplicateTestNames,
+ _allowTestRandomization = allowTestRandomization,
+ _jsTrace = jsTrace,
+ _runSkipped = runSkipped,
+ dart2jsArgs = _list(dart2jsArgs) ?? const [],
+ _runtimes = _list(runtimes),
+ compilerSelections = _list(compilerSelections),
+ tags = _map(tags),
+ onPlatform = _map(onPlatform),
+ _ignoreTimeouts = ignoreTimeouts,
+ _metadata = metadata ?? Metadata.empty;
+
+ /// Creates a new [SuiteConfiguration] that takes its configuration from
+ /// [metadata].
+ factory SuiteConfiguration.fromMetadata(Metadata metadata) =>
+ SuiteConfiguration._(
+ tags: metadata.forTag.map((key, child) =>
+ MapEntry(key, SuiteConfiguration.fromMetadata(child))),
+ onPlatform: metadata.onPlatform.map((key, child) =>
+ MapEntry(key, SuiteConfiguration.fromMetadata(child))),
+ metadata: metadata.change(forTag: {}, onPlatform: {}),
+ allowDuplicateTestNames: null,
+ allowTestRandomization: null,
+ jsTrace: null,
+ runSkipped: null,
+ dart2jsArgs: null,
+ testSelections: const {},
+ precompiledPath: null,
+ runtimes: null,
+ compilerSelections: null,
+ ignoreTimeouts: null,
+ );
+
+ /// Returns an unmodifiable copy of [input].
+ ///
+ /// If [input] is `null` or empty, this returns `null`.
+ static List<T>? _list<T>(Iterable<T>? input) {
+ if (input == null) return null;
+ var list = List<T>.unmodifiable(input);
+ if (list.isEmpty) return null;
+ return list;
+ }
+
+ /// Returns an unmodifiable copy of [input] or an empty unmodifiable map.
+ static Map<K, V> _map<K, V>(Map<K, V>? input) {
+ if (input == null || input.isEmpty) return const <Never, Never>{};
+ return Map.unmodifiable(input);
+ }
+
+ /// Merges this with [other].
+ ///
+ /// For most fields, if both configurations have values set, [other]'s value
+ /// takes precedence. However, certain fields are merged together instead.
+ /// This is indicated in those fields' documentation.
+ SuiteConfiguration merge(SuiteConfiguration other) {
+ if (this == SuiteConfiguration.empty) return other;
+ if (other == SuiteConfiguration.empty) return this;
+ assert(testSelections.isEmpty || other.testSelections.isEmpty);
+
+ var config = SuiteConfiguration._(
+ allowDuplicateTestNames:
+ other._allowDuplicateTestNames ?? _allowDuplicateTestNames,
+ allowTestRandomization:
+ other._allowTestRandomization ?? _allowTestRandomization,
+ jsTrace: other._jsTrace ?? _jsTrace,
+ runSkipped: other._runSkipped ?? _runSkipped,
+ dart2jsArgs: dart2jsArgs.toList()..addAll(other.dart2jsArgs),
+ testSelections:
+ testSelections.isEmpty ? other.testSelections : testSelections,
+ precompiledPath: other.precompiledPath ?? precompiledPath,
+ compilerSelections: other.compilerSelections ?? compilerSelections,
+ runtimes: other._runtimes ?? _runtimes,
+ tags: _mergeConfigMaps(tags, other.tags),
+ onPlatform: _mergeConfigMaps(onPlatform, other.onPlatform),
+ ignoreTimeouts: other._ignoreTimeouts ?? _ignoreTimeouts,
+ metadata: metadata.merge(other.metadata));
+ return config._resolveTags();
+ }
+
+ /// Returns a copy of this configuration with the given fields updated.
+ ///
+ /// Note that unlike [merge], this has no merging behavior—the old value is
+ /// always replaced by the new one.
+ SuiteConfiguration change(
+ {bool? allowDuplicateTestNames,
+ bool? allowTestRandomization,
+ bool? jsTrace,
+ bool? runSkipped,
+ Iterable<String>? dart2jsArgs,
+ String? precompiledPath,
+ Iterable<CompilerSelection>? compilerSelections,
+ Iterable<RuntimeSelection>? runtimes,
+ Map<BooleanSelector, SuiteConfiguration>? tags,
+ Map<PlatformSelector, SuiteConfiguration>? onPlatform,
+ bool? ignoreTimeouts,
+
+ // Test-level configuration
+ Timeout? timeout,
+ bool? verboseTrace,
+ bool? chainStackTraces,
+ bool? skip,
+ int? retry,
+ String? skipReason,
+ PlatformSelector? testOn,
+ Iterable<String>? addTags}) {
+ var config = SuiteConfiguration._(
+ allowDuplicateTestNames:
+ allowDuplicateTestNames ?? _allowDuplicateTestNames,
+ allowTestRandomization:
+ allowTestRandomization ?? _allowTestRandomization,
+ jsTrace: jsTrace ?? _jsTrace,
+ runSkipped: runSkipped ?? _runSkipped,
+ dart2jsArgs: dart2jsArgs?.toList() ?? this.dart2jsArgs,
+ testSelections: testSelections,
+ precompiledPath: precompiledPath ?? this.precompiledPath,
+ compilerSelections: compilerSelections ?? this.compilerSelections,
+ runtimes: runtimes ?? _runtimes,
+ tags: tags ?? this.tags,
+ onPlatform: onPlatform ?? this.onPlatform,
+ ignoreTimeouts: ignoreTimeouts ?? _ignoreTimeouts,
+ metadata: _metadata.change(
+ timeout: timeout,
+ verboseTrace: verboseTrace,
+ chainStackTraces: chainStackTraces,
+ skip: skip,
+ retry: retry,
+ skipReason: skipReason,
+ testOn: testOn,
+ tags: addTags?.toSet()));
+ return config._resolveTags();
+ }
+
+ /// Assign the selection of tests that should run for this suite.
+ ///
+ /// Test selections must be chosen only once per suite, once a
+ /// SuiteConfiguration has test slections this method should not be called
+ /// again.
+ ///
+ /// [testSelections] must not be empty.
+ SuiteConfiguration selectTests(Set<TestSelection> testSelections) {
+ assert(this.testSelections.isEmpty);
+ assert(testSelections.isNotEmpty);
+ return SuiteConfiguration._(
+ testSelections: testSelections,
+ allowDuplicateTestNames: _allowDuplicateTestNames,
+ allowTestRandomization: _allowTestRandomization,
+ jsTrace: _jsTrace,
+ runSkipped: _runSkipped,
+ dart2jsArgs: dart2jsArgs,
+ precompiledPath: precompiledPath,
+ compilerSelections: compilerSelections,
+ runtimes: _runtimes,
+ tags: tags,
+ onPlatform: onPlatform,
+ ignoreTimeouts: _ignoreTimeouts,
+ metadata: _metadata);
+ }
+
+ /// Throws a [FormatException] if this refers to any undefined runtimes.
+ void validateRuntimes(List<Runtime> allRuntimes) {
+ var validVariables =
+ allRuntimes.map((runtime) => runtime.identifier).toSet();
+ _metadata.validatePlatformSelectors(validVariables);
+
+ var runtimes = _runtimes;
+ if (runtimes != null) {
+ for (var selection in runtimes) {
+ if (!allRuntimes
+ .any((runtime) => runtime.identifier == selection.name)) {
+ if (selection.span != null) {
+ throw SourceSpanFormatException(
+ 'Unknown platform "${selection.name}".', selection.span);
+ } else {
+ throw FormatException('Unknown platform "${selection.name}".');
+ }
+ }
+ }
+ }
+
+ onPlatform.forEach((selector, config) {
+ selector.validate(validVariables);
+ config.validateRuntimes(allRuntimes);
+ });
+ }
+
+ /// Returns a copy of this with all platform-specific configuration from
+ /// [onPlatform] resolved.
+ SuiteConfiguration forPlatform(SuitePlatform platform) {
+ if (onPlatform.isEmpty) return this;
+
+ var config = this;
+ onPlatform.forEach((platformSelector, platformConfig) {
+ if (!platformSelector.evaluate(platform)) return;
+ config = config.merge(platformConfig);
+ });
+ return config.change(onPlatform: {});
+ }
+
+ /// Merges two maps whose values are [SuiteConfiguration]s.
+ ///
+ /// Any overlapping keys in the maps have their configurations merged in the
+ /// returned map.
+ Map<T, SuiteConfiguration> _mergeConfigMaps<T>(
+ Map<T, SuiteConfiguration> map1, Map<T, SuiteConfiguration> map2) =>
+ mergeMaps(map1, map2,
+ value: (config1, config2) => config1.merge(config2));
+
+ SuiteConfiguration _resolveTags() {
+ // If there's no tag-specific configuration, or if none of it applies, just
+ // return the configuration as-is.
+ if (_metadata.tags.isEmpty || tags.isEmpty) return this;
+
+ // Otherwise, resolve the tag-specific components.
+ var newTags = Map<BooleanSelector, SuiteConfiguration>.from(tags);
+ var merged = tags.keys.fold(empty, (SuiteConfiguration merged, selector) {
+ if (!selector.evaluate(_metadata.tags.contains)) return merged;
+ return merged.merge(newTags.remove(selector)!);
+ });
+
+ if (merged == empty) return this;
+ return change(tags: newTags).merge(merged);
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/util/iterable_set.dart b/pkgs/test_core/lib/src/runner/util/iterable_set.dart
new file mode 100644
index 0000000..09335ba
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/util/iterable_set.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2016, 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:collection';
+
+import 'package:collection/collection.dart';
+
+/// An unmodifiable [Set] view backed by an arbitrary [Iterable].
+///
+/// Note that contrary to most APIs that take iterables, this does not convert
+/// its argument to another collection before use. This means that if it's
+/// lazily-generated, that generation will happen for every operation.
+///
+/// Note also that set operations that are usually expected to be `O(1)` or
+/// `O(log(n))`, such as [contains], may be `O(n)` for many underlying iterable
+/// types. As such, this should only be used for small iterables.
+class IterableSet<E> with SetMixin<E>, UnmodifiableSetMixin<E> {
+ /// The base iterable that set operations forward to.
+ final Iterable<E> _base;
+
+ @override
+ int get length => _base.length;
+
+ @override
+ Iterator<E> get iterator => _base.iterator;
+
+ /// Creates a [Set] view of [base].
+ IterableSet(this._base);
+
+ @override
+ bool contains(Object? element) => _base.contains(element);
+
+ @override
+ E? lookup(Object? element) {
+ for (var e in _base) {
+ if (e == element) return e;
+ }
+ return null;
+ }
+
+ @override
+ Set<E> toSet() => _base.toSet();
+}
diff --git a/pkgs/test_core/lib/src/runner/version.dart b/pkgs/test_core/lib/src/runner/version.dart
new file mode 100644
index 0000000..3eab242
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/version.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2015, 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:io';
+
+import 'package:yaml/yaml.dart';
+
+/// The version number of the test runner, or `null` if it couldn't be loaded.
+///
+/// This is a semantic version, optionally followed by a space and additional
+/// data about its source.
+final String? testVersion = (() {
+ dynamic lockfile;
+ try {
+ lockfile = loadYaml(File('pubspec.lock').readAsStringSync());
+ } on FormatException catch (_) {
+ return null;
+ } on IOException catch (_) {
+ return null;
+ }
+
+ if (lockfile is! Map) return null;
+ var packages = lockfile['packages'];
+ if (packages is! Map) return null;
+ var package = packages['test'];
+ if (package is! Map) return null;
+
+ var source = package['source'];
+ if (source is! String) return null;
+
+ switch (source) {
+ case 'hosted':
+ var version = package['version'];
+ return (version is String) ? version : null;
+
+ case 'git':
+ var version = package['version'];
+ if (version is! String) return null;
+ var description = package['description'];
+ if (description is! Map) return null;
+ var ref = description['resolved-ref'];
+ if (ref is! String) return null;
+
+ return '$version (${ref.substring(0, 7)})';
+
+ case 'path':
+ var version = package['version'];
+ if (version is! String) return null;
+ var description = package['description'];
+ if (description is! Map) return null;
+ var path = description['path'];
+ if (path is! String) return null;
+
+ return '$version (from $path)';
+
+ default:
+ return null;
+ }
+})();
diff --git a/pkgs/test_core/lib/src/runner/vm/environment.dart b/pkgs/test_core/lib/src/runner/vm/environment.dart
new file mode 100644
index 0000000..76cc2fd
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/vm/environment.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2018, 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 'package:async/async.dart';
+import 'package:vm_service/vm_service.dart';
+
+import '../environment.dart'; // ignore: implementation_imports
+
+/// The environment in which VM tests are loaded.
+class VMEnvironment implements Environment {
+ @override
+ final supportsDebugging = true;
+ @override
+ final Uri observatoryUrl;
+
+ /// The VM service isolate object used to control this isolate.
+ final IsolateRef _isolate;
+ final VmService _client;
+
+ VMEnvironment(this.observatoryUrl, this._isolate, this._client);
+
+ @override
+ Uri? get remoteDebuggerUrl => null;
+
+ @override
+ Stream<void> get onRestart => StreamController<void>.broadcast().stream;
+
+ @override
+ CancelableOperation<void> displayPause() {
+ var completer =
+ CancelableCompleter<void>(onCancel: () => _client.resume(_isolate.id!));
+
+ completer.complete(_client.pause(_isolate.id!).then((_) => _client
+ .onDebugEvent
+ .firstWhere((event) => event.kind == EventKind.kResume)));
+
+ return completer.operation;
+ }
+}
diff --git a/pkgs/test_core/lib/src/runner/vm/platform.dart b/pkgs/test_core/lib/src/runner/vm/platform.dart
new file mode 100644
index 0000000..506909d
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/vm/platform.dart
@@ -0,0 +1,357 @@
+// Copyright (c) 2016, 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:developer';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:async/async.dart';
+import 'package:coverage/coverage.dart';
+import 'package:path/path.dart' as p;
+import 'package:stream_channel/isolate_channel.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test_api/backend.dart';
+import 'package:vm_service/vm_service.dart' hide Isolate;
+import 'package:vm_service/vm_service_io.dart';
+
+import '../../runner/configuration.dart';
+import '../../runner/environment.dart';
+import '../../runner/load_exception.dart';
+import '../../runner/platform.dart';
+import '../../runner/plugin/platform_helpers.dart';
+import '../../runner/plugin/shared_platform_helpers.dart';
+import '../../runner/runner_suite.dart';
+import '../../runner/suite.dart';
+import '../../util/io.dart';
+import '../../util/package_config.dart';
+import '../package_version.dart';
+import 'environment.dart';
+import 'test_compiler.dart';
+
+var _shouldPauseAfterTests = false;
+
+/// A platform that loads tests in isolates spawned within this Dart process.
+class VMPlatform extends PlatformPlugin {
+ /// The test runner configuration.
+ final _config = Configuration.current;
+ final _compiler = TestCompiler(
+ p.join(p.current, '.dart_tool', 'test', 'incremental_kernel'));
+ final _closeMemo = AsyncMemoizer<void>();
+ final _tempDir = Directory.systemTemp.createTempSync('dart_test.vm.');
+
+ @override
+ Future<RunnerSuite?> load(String path, SuitePlatform platform,
+ SuiteConfiguration suiteConfig, Map<String, Object?> message) async {
+ assert(platform.runtime == Runtime.vm);
+
+ _setupPauseAfterTests();
+
+ MultiChannel outerChannel;
+ var cleanupCallbacks = <void Function()>[];
+ Isolate? isolate;
+ if (platform.compiler == Compiler.exe) {
+ var serverSocket = await ServerSocket.bind('localhost', 0);
+ Process process;
+ try {
+ process =
+ await _spawnExecutable(path, suiteConfig.metadata, serverSocket);
+ } catch (error) {
+ unawaited(serverSocket.close());
+ rethrow;
+ }
+ process.stdout.listen(stdout.add);
+ process.stderr.listen(stderr.add);
+ var socket = await serverSocket.first;
+ outerChannel = MultiChannel<Object?>(jsonSocketStreamChannel(socket));
+ cleanupCallbacks
+ ..add(serverSocket.close)
+ ..add(process.kill);
+ } else {
+ var receivePort = ReceivePort();
+ try {
+ isolate = await _spawnIsolate(path, receivePort.sendPort,
+ suiteConfig.metadata, platform.compiler);
+ if (isolate == null) return null;
+ } catch (error) {
+ receivePort.close();
+ rethrow;
+ }
+ outerChannel = MultiChannel(IsolateChannel.connectReceive(receivePort));
+ cleanupCallbacks.add(isolate.kill);
+ }
+ cleanupCallbacks.add(outerChannel.sink.close);
+
+ VmService? client;
+ StreamSubscription<Event>? eventSub;
+ // Typical test interaction will go across `channel`, `outerChannel` adds
+ // additional communication directly between the test bootstrapping and this
+ // platform to enable pausing after tests for debugging.
+ var outerQueue = StreamQueue(outerChannel.stream);
+ var channelId = (await outerQueue.next) as int;
+ var channel = outerChannel.virtualChannel(channelId).transformStream(
+ StreamTransformer.fromHandlers(handleDone: (sink) async {
+ if (_shouldPauseAfterTests) {
+ outerChannel.sink.add('debug');
+ await outerQueue.next;
+ }
+ for (var fn in cleanupCallbacks) {
+ fn();
+ }
+ unawaited(eventSub?.cancel());
+ unawaited(client?.dispose());
+ sink.close();
+ }));
+
+ Environment? environment;
+ IsolateRef? isolateRef;
+ if (_config.debug) {
+ if (platform.compiler == Compiler.exe) {
+ throw UnsupportedError(
+ 'Unable to debug tests compiled to `exe` (tried to debug $path with '
+ 'the `exe` compiler).');
+ }
+ var info =
+ await Service.controlWebServer(enable: true, silenceOutput: true);
+ // ignore: deprecated_member_use, Remove when SDK constraint is at 3.2.0
+ var isolateID = Service.getIsolateID(isolate!)!;
+
+ var libraryPath = (await absoluteUri(path)).toString();
+ var serverUri = info.serverUri!;
+ client = await vmServiceConnectUri(_wsUriFor(serverUri).toString());
+ var isolateNumber = int.parse(isolateID.split('/').last);
+ isolateRef = (await client.getVM())
+ .isolates!
+ .firstWhere((isolate) => isolate.number == isolateNumber.toString());
+ await client.setName(isolateRef.id!, path);
+ var libraryRef = (await client.getIsolate(isolateRef.id!))
+ .libraries!
+ .firstWhere((library) => library.uri == libraryPath);
+ var url = _observatoryUrlFor(serverUri, isolateRef.id!, libraryRef.id!);
+ environment = VMEnvironment(url, isolateRef, client);
+ }
+
+ environment ??= const PluginEnvironment();
+
+ var controller = deserializeSuite(
+ path, platform, suiteConfig, environment, channel.cast(), message,
+ gatherCoverage: () => _gatherCoverage(environment!));
+
+ if (isolateRef != null) {
+ await client!.streamListen('Debug');
+ eventSub = client.onDebugEvent.listen((event) {
+ if (event.kind == EventKind.kResume) {
+ controller.setDebugging(false);
+ } else if (event.kind == EventKind.kPauseInterrupted ||
+ event.kind == EventKind.kPauseBreakpoint ||
+ event.kind == EventKind.kPauseException) {
+ controller.setDebugging(true);
+ }
+ });
+ }
+
+ return await controller.suite;
+ }
+
+ @override
+ Future close() => _closeMemo.runOnce(() => Future.wait([
+ _compiler.dispose(),
+ _tempDir.deleteWithRetry(),
+ ]));
+
+ /// Compiles [path] to a native executable and spawns it as a process.
+ ///
+ /// Sets up a communication channel as well by passing command line arguments
+ /// for the host and port of [socket].
+ Future<Process> _spawnExecutable(
+ String path, Metadata suiteMetadata, ServerSocket socket) async {
+ if (_config.suiteDefaults.precompiledPath != null) {
+ throw UnsupportedError(
+ 'Precompiled native executable tests are not supported at this time');
+ }
+ var executable = await _compileToNative(path, suiteMetadata);
+ return await Process.start(
+ executable, [socket.address.host, socket.port.toString()]);
+ }
+
+ /// Compiles [path] to a native executable using `dart compile exe`.
+ Future<String> _compileToNative(String path, Metadata suiteMetadata) async {
+ var bootstrapPath = await _bootstrapNativeTestFile(
+ path,
+ suiteMetadata.languageVersionComment ??
+ await rootPackageLanguageVersionComment);
+ var output = File(p.setExtension(bootstrapPath, '.exe'));
+ var processResult = await Process.run(Platform.resolvedExecutable, [
+ 'compile',
+ 'exe',
+ bootstrapPath,
+ '--output',
+ output.path,
+ '--packages',
+ (await packageConfigUri).toFilePath(),
+ ]);
+ if (processResult.exitCode != 0 || !(await output.exists())) {
+ throw LoadException(path, '''
+exitCode: ${processResult.exitCode}
+stdout: ${processResult.stdout}
+stderr: ${processResult.stderr}''');
+ }
+ return output.path;
+ }
+
+ /// Spawns an isolate with the current configuration and passes it [message].
+ ///
+ /// This isolate connects an [IsolateChannel] to [message] and sends the
+ /// serialized tests over that channel.
+ ///
+ /// Returns `null` if an exception occurs but [close] has already been called.
+ Future<Isolate?> _spawnIsolate(String path, SendPort message,
+ Metadata suiteMetadata, Compiler compiler) async {
+ try {
+ var precompiledPath = _config.suiteDefaults.precompiledPath;
+ if (precompiledPath != null) {
+ return _spawnPrecompiledIsolate(
+ path, message, precompiledPath, compiler);
+ }
+ return switch (compiler) {
+ Compiler.kernel => _spawnIsolateWithUri(
+ await _compileToKernel(path, suiteMetadata), message),
+ Compiler.source => _spawnIsolateWithUri(
+ await _bootstrapIsolateTestFile(
+ path,
+ suiteMetadata.languageVersionComment ??
+ await rootPackageLanguageVersionComment),
+ message),
+ _ => throw StateError(
+ 'Unsupported compiler $compiler for the VM platform'),
+ };
+ } catch (_) {
+ if (_closeMemo.hasRun) return null;
+ rethrow;
+ }
+ }
+
+ /// Compiles [path] to kernel and returns the uri to the compiled dill.
+ Future<Uri> _compileToKernel(String path, Metadata suiteMetadata) async {
+ final response =
+ await _compiler.compile(await absoluteUri(path), suiteMetadata);
+ var compiledDill = response.kernelOutputUri?.toFilePath();
+ if (compiledDill == null || response.errorCount > 0) {
+ throw LoadException(path, response.compilerOutput ?? 'unknown error');
+ }
+ return absoluteUri(compiledDill);
+ }
+
+ /// Runs [uri] in an isolate, passing [message].
+ Future<Isolate> _spawnIsolateWithUri(Uri uri, SendPort message) async {
+ return await Isolate.spawnUri(uri, [], message,
+ packageConfig: await packageConfigUri, checked: true);
+ }
+
+ Future<Isolate> _spawnPrecompiledIsolate(String testPath, SendPort message,
+ String precompiledPath, Compiler compiler) async {
+ var testUri =
+ await absoluteUri('${p.join(precompiledPath, testPath)}.vm_test.dart');
+ testUri = testUri.replace(path: testUri.path.stripDriveLetterLeadingSlash);
+
+ switch (compiler) {
+ case Compiler.kernel:
+ // Load `.dill` files from their absolute file path.
+ var dillUri = (await Isolate.resolvePackageUri(testUri.replace(
+ path:
+ '${testUri.path.substring(0, testUri.path.length - '.dart'.length)}'
+ '.vm.app.dill')))!;
+ if (await File.fromUri(dillUri).exists()) {
+ testUri = dillUri;
+ }
+ // TODO: Compile to kernel manually here? Otherwise we aren't compiling
+ // with kernel when we technically should be, based on the compiler
+ // setting.
+ break;
+ case Compiler.source:
+ // Just leave test uri as is.
+ break;
+ default:
+ throw StateError('Unsupported compiler for the VM platform $compiler.');
+ }
+ File? packageConfig =
+ File(p.join(precompiledPath, '.dart_tool/package_config.json'));
+ if (!(await packageConfig.exists())) {
+ packageConfig = File(p.join(precompiledPath, '.packages'));
+ if (!(await packageConfig.exists())) {
+ packageConfig = null;
+ }
+ }
+ return await Isolate.spawnUri(testUri, [], message,
+ packageConfig: packageConfig?.uri, checked: true);
+ }
+
+ /// Bootstraps the test at [testPath] and writes its contents to a temporary
+ /// file.
+ ///
+ /// Returns the [Uri] to the created file.
+ Future<Uri> _bootstrapIsolateTestFile(
+ String testPath, String languageVersionComment) async {
+ var file = File(p.join(
+ _tempDir.path, p.setExtension(testPath, '.bootstrap.isolate.dart')));
+ if (!file.existsSync()) {
+ file
+ ..createSync(recursive: true)
+ ..writeAsStringSync(testBootstrapContents(
+ testUri: await absoluteUri(testPath),
+ languageVersionComment: languageVersionComment,
+ packageConfigUri: await packageConfigUri,
+ testType: VmTestType.isolate,
+ ));
+ }
+ return file.uri;
+ }
+
+ /// Bootstraps the test at [testPath] for native execution and writes its
+ /// contents to a temporary file.
+ ///
+ /// Returns the path to the created file.
+ Future<String> _bootstrapNativeTestFile(
+ String testPath, String languageVersionComment) async {
+ var file = File(p.join(
+ _tempDir.path, p.setExtension(testPath, '.bootstrap.native.dart')));
+ if (!file.existsSync()) {
+ file
+ ..createSync(recursive: true)
+ ..writeAsStringSync(testBootstrapContents(
+ testUri: await absoluteUri(testPath),
+ languageVersionComment: languageVersionComment,
+ packageConfigUri: await packageConfigUri,
+ testType: VmTestType.process,
+ ));
+ }
+ return file.path;
+ }
+}
+
+Future<Map<String, dynamic>> _gatherCoverage(Environment environment) async {
+ final isolateId = Uri.parse(environment.observatoryUrl!.fragment)
+ .queryParameters['isolateId'];
+ return await collect(environment.observatoryUrl!, false, false, false, {},
+ isolateIds: {isolateId!});
+}
+
+Uri _wsUriFor(Uri observatoryUrl) =>
+ observatoryUrl.replace(scheme: 'ws').resolve('ws');
+
+Uri _observatoryUrlFor(Uri base, String isolateId, String id) => base.replace(
+ fragment: Uri(
+ path: '/inspect',
+ queryParameters: {'isolateId': isolateId, 'objectId': id}).toString());
+
+var _hasRegistered = false;
+void _setupPauseAfterTests() {
+ if (_hasRegistered) return;
+ _hasRegistered = true;
+ registerExtension('ext.test.pauseAfterTests', (_, __) async {
+ _shouldPauseAfterTests = true;
+ return ServiceExtensionResponse.result(jsonEncode({}));
+ });
+}
diff --git a/pkgs/test_core/lib/src/runner/vm/test_compiler.dart b/pkgs/test_core/lib/src/runner/vm/test_compiler.dart
new file mode 100644
index 0000000..56fbbd2
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/vm/test_compiler.dart
@@ -0,0 +1,238 @@
+// Copyright (c) 2020, 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:async/async.dart';
+import 'package:frontend_server_client/frontend_server_client.dart';
+import 'package:path/path.dart' as p;
+import 'package:pool/pool.dart';
+import 'package:test_api/backend.dart';
+
+import '../../util/dart.dart';
+import '../../util/io.dart';
+import '../../util/package_config.dart';
+import '../package_version.dart';
+
+class CompilationResponse {
+ final String? compilerOutput;
+ final int errorCount;
+ final Uri? kernelOutputUri;
+
+ const CompilationResponse(
+ {this.compilerOutput, this.errorCount = 0, this.kernelOutputUri});
+
+ static const _wasShutdown = CompilationResponse(
+ errorCount: 1, compilerOutput: 'Compiler no longer active.');
+}
+
+class TestCompiler {
+ final _closeMemo = AsyncMemoizer<void>();
+
+ /// Each language version that appears in test files gets its own compiler,
+ /// to ensure that all language modes are supported (such as sound and
+ /// unsound null safety).
+ final _compilerForLanguageVersion =
+ <String, _TestCompilerForLanguageVersion>{};
+
+ /// A prefix used for the dill files for each compiler that is created.
+ final String _dillCachePrefix;
+
+ /// No work is done until the first call to [compile] is received, at which
+ /// point the compiler process is started.
+ TestCompiler(this._dillCachePrefix);
+
+ /// Compiles [mainDart], using a separate compiler per language version of
+ /// the tests.
+ Future<CompilationResponse> compile(Uri mainDart, Metadata metadata) async {
+ if (_closeMemo.hasRun) return CompilationResponse._wasShutdown;
+ var languageVersionComment = metadata.languageVersionComment ??
+ await rootPackageLanguageVersionComment;
+ var compiler = _compilerForLanguageVersion.putIfAbsent(
+ languageVersionComment,
+ () => _TestCompilerForLanguageVersion(
+ _dillCachePrefix, languageVersionComment));
+ return compiler.compile(mainDart);
+ }
+
+ Future<void> dispose() => _closeMemo.runOnce(() => Future.wait([
+ for (var compiler in _compilerForLanguageVersion.values)
+ compiler.dispose(),
+ ]));
+}
+
+class _TestCompilerForLanguageVersion {
+ final _closeMemo = AsyncMemoizer<void>();
+ final _compilePool = Pool(1);
+ final String _dillCachePath;
+ FrontendServerClient? _frontendServerClient;
+ final String _languageVersionComment;
+ late final _outputDill =
+ File(p.join(_outputDillDirectory.path, 'output.dill'));
+ final _outputDillDirectory =
+ Directory.systemTemp.createTempSync('dart_test.kernel.');
+ // Used to create unique file names for final kernel files.
+ int _compileNumber = 0;
+ // The largest incremental dill file we created, will be cached under
+ // the `.dart_tool` dir at the end of compilation.
+ File? _dillToCache;
+
+ _TestCompilerForLanguageVersion(
+ String dillCachePrefix, this._languageVersionComment)
+ : _dillCachePath = '$dillCachePrefix.'
+ '${_dillCacheSuffix(_languageVersionComment, enabledExperiments)}';
+
+ Future<CompilationResponse> compile(Uri mainUri) =>
+ _compilePool.withResource(() => _compile(mainUri));
+
+ Future<CompilationResponse> _compile(Uri mainUri) async {
+ _compileNumber++;
+ if (_closeMemo.hasRun) return CompilationResponse._wasShutdown;
+ CompileResult? compilerOutput;
+ final tempFile = File(p.join(_outputDillDirectory.path, 'test.dart'))
+ ..writeAsStringSync(testBootstrapContents(
+ testUri: mainUri,
+ packageConfigUri: await packageConfigUri,
+ languageVersionComment: _languageVersionComment,
+ testType: VmTestType.isolate,
+ ));
+ final testCache = File(_dillCachePath);
+
+ try {
+ if (_frontendServerClient == null) {
+ if (await testCache.exists()) {
+ await testCache.copy(_outputDill.path);
+ }
+ compilerOutput = await _createCompiler(tempFile.uri);
+ } else {
+ compilerOutput =
+ await _frontendServerClient!.compile(<Uri>[tempFile.uri]);
+ }
+ } catch (e, s) {
+ if (_closeMemo.hasRun) return CompilationResponse._wasShutdown;
+ return CompilationResponse(errorCount: 1, compilerOutput: '$e\n$s');
+ } finally {
+ _frontendServerClient?.accept();
+ _frontendServerClient?.reset();
+ }
+
+ // The client is guaranteed initialized at this point.
+ final outputPath = compilerOutput?.dillOutput;
+ if (outputPath == null) {
+ return CompilationResponse(
+ compilerOutput: compilerOutput?.compilerOutputLines.join('\n'),
+ errorCount: compilerOutput?.errorCount ?? 0);
+ }
+
+ final outputFile = File(outputPath);
+ final kernelReadyToRun =
+ await outputFile.copy('${tempFile.path}_$_compileNumber.dill');
+ // Keep the `_dillToCache` file up-to-date and use the size of the
+ // kernel file as an approximation for how many packages are included.
+ // Larger files are preferred, since re-using more packages will reduce the
+ // number of files the frontend server needs to load and parse.
+ if (_dillToCache == null ||
+ (_dillToCache!.lengthSync() < kernelReadyToRun.lengthSync())) {
+ _dillToCache = kernelReadyToRun;
+ }
+
+ return CompilationResponse(
+ compilerOutput: compilerOutput?.compilerOutputLines.join('\n'),
+ errorCount: compilerOutput?.errorCount ?? 0,
+ kernelOutputUri: kernelReadyToRun.absolute.uri);
+ }
+
+ Future<CompileResult?> _createCompiler(Uri testUri) async {
+ final platformDill = 'lib/_internal/vm_platform_strong.dill';
+ final sdkRoot =
+ p.relative(p.dirname(p.dirname(Platform.resolvedExecutable)));
+ final packageConfigUriAwaited = await packageConfigUri;
+
+ // If we have native assets for the host os in JIT mode, they are here.
+ Uri? nativeAssetsYaml;
+ if (enabledExperiments.contains('native-assets')) {
+ nativeAssetsYaml = packageConfigUriAwaited.resolve('native_assets.yaml');
+ if (!await File.fromUri(nativeAssetsYaml).exists()) {
+ nativeAssetsYaml = null;
+ }
+ }
+
+ var client = _frontendServerClient = await FrontendServerClient.start(
+ testUri.toString(),
+ _outputDill.path,
+ platformDill,
+ enabledExperiments: enabledExperiments,
+ sdkRoot: sdkRoot,
+ packagesJson: packageConfigUriAwaited.toFilePath(),
+ nativeAssets: nativeAssetsYaml?.toFilePath(),
+ printIncrementalDependencies: false,
+ );
+ return client.compile();
+ }
+
+ Future<void> dispose() => _closeMemo.runOnce(() async {
+ await _compilePool.close();
+ if (_dillToCache != null) {
+ var testCache = File(_dillCachePath);
+ if (!testCache.parent.existsSync()) {
+ testCache.parent.createSync(recursive: true);
+ }
+ _dillToCache!.copySync(_dillCachePath);
+ }
+ _frontendServerClient?.kill();
+ _frontendServerClient = null;
+ if (_outputDillDirectory.existsSync()) {
+ await _outputDillDirectory.deleteWithRetry();
+ }
+ });
+}
+
+/// Computes a unique dill cache suffix for each [languageVersionComment]
+/// and [enabledExperiments] combination.
+String _dillCacheSuffix(
+ String languageVersionComment, List<String> enabledExperiments) {
+ var identifierString =
+ StringBuffer(languageVersionComment.replaceAll(' ', ''));
+ for (var experiment in enabledExperiments) {
+ identifierString.writeln(experiment);
+ }
+ return base64.encode(utf8.encode(identifierString.toString()));
+}
+
+/// Creates bootstrap file contents for running [testUri].
+///
+/// The [bootstrapType] argument should be either 'Vm' or 'Native' depending on
+/// which `internalBootstrap*Test` function should be used.
+String testBootstrapContents({
+ required Uri testUri,
+ required String languageVersionComment,
+ required Uri packageConfigUri,
+ required VmTestType testType,
+}) {
+ final (mainArgs, forwardedArgName, bootstrapType) = switch (testType) {
+ VmTestType.isolate => ('_, SendPort sendPort', 'sendPort', 'Vm'),
+ VmTestType.process => ('List<String> args', 'args', 'Native'),
+ };
+ return '''
+ $languageVersionComment
+
+ import 'dart:isolate';
+
+ import 'package:test_core/src/bootstrap/vm.dart';
+
+ import '$testUri' as test;
+
+ // This variable is read at runtime through the VM service and is unsafe to
+ // remove.
+ const packageConfigLocation = '$packageConfigUri';
+
+ void main($mainArgs) {
+ internalBootstrap${bootstrapType}Test(() => test.main, $forwardedArgName);
+ }
+ ''';
+}
+
+enum VmTestType { isolate, process }
diff --git a/pkgs/test_core/lib/src/runner/wasm_compiler_pool.dart b/pkgs/test_core/lib/src/runner/wasm_compiler_pool.dart
new file mode 100644
index 0000000..c4318f5
--- /dev/null
+++ b/pkgs/test_core/lib/src/runner/wasm_compiler_pool.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2022, 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:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+import '../util/dart.dart';
+import '../util/io.dart';
+import '../util/package_config.dart';
+import 'compiler_pool.dart';
+import 'suite.dart';
+
+/// A pool of `dart2wasm` compiler instances.
+///
+/// This limits the number of compiler instances running concurrently.
+class WasmCompilerPool extends CompilerPool {
+ /// Extra arguments to pass to `dart compile js`.
+ final List<String> _extraArgs;
+
+ /// The currently-active dart2wasm processes.
+ final _processes = <Process>{};
+
+ WasmCompilerPool([this._extraArgs = const []]);
+
+ /// Compiles [code] to [path].
+ ///
+ /// This wraps the Dart code in the standard browser-testing wrapper.
+ ///
+ /// The returned [Future] will complete once the `dart2wasm` process completes
+ /// *and* all its output has been printed to the command line.
+ @override
+ Future compileInternal(
+ String code, String path, SuiteConfiguration suiteConfig) {
+ return withTempDir((dir) async {
+ final wrapperPath = p.join(dir, 'main.dart');
+ File(wrapperPath).writeAsStringSync(code);
+ final outWasmPath = '$path.wasm';
+ final process = await Process.start(Platform.resolvedExecutable, [
+ 'compile',
+ 'wasm',
+ '--enable-asserts',
+ '--packages=${(await packageConfigUri).toFilePath()}',
+ for (var experiment in enabledExperiments)
+ '--enable-experiment=$experiment',
+ '-O0',
+ ..._extraArgs,
+ '-o',
+ outWasmPath,
+ wrapperPath,
+ ]);
+ if (closed) {
+ process.kill();
+ return;
+ }
+
+ _processes.add(process);
+
+ /// Wait until the process is entirely done to print out any output.
+ /// This can produce a little extra time for users to wait with no
+ /// update, but it also avoids some really nasty-looking interleaved
+ /// output. Write both stdout and stderr to the same buffer in case
+ /// they're intended to be printed in order.
+ var buffer = StringBuffer();
+
+ await Future.wait([
+ process.stdout.transform(utf8.decoder).forEach(buffer.write),
+ process.stderr.transform(utf8.decoder).forEach(buffer.write),
+ ]);
+
+ var exitCode = await process.exitCode;
+ _processes.remove(process);
+ if (closed) return;
+
+ var output = buffer.toString();
+ if (output.isNotEmpty) print(output);
+
+ if (exitCode != 0) throw StateError('dart2wasm failed.');
+ });
+ }
+
+ /// Closes the compiler pool.
+ ///
+ /// This kills all currently-running compilers and ensures that no more will
+ /// be started. It returns a [Future] that completes once all the compilers
+ /// have been killed and all resources released.
+ @override
+ Future<void> closeInternal() async {
+ await Future.wait(_processes.map((process) async {
+ process.kill();
+ await process.exitCode;
+ }));
+ }
+}
diff --git a/pkgs/test_core/lib/src/scaffolding.dart b/pkgs/test_core/lib/src/scaffolding.dart
new file mode 100644
index 0000000..31b3d89
--- /dev/null
+++ b/pkgs/test_core/lib/src/scaffolding.dart
@@ -0,0 +1,295 @@
+// Copyright (c) 2021, 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 'package:meta/meta.dart' show doNotSubmit, isTest, isTestGroup;
+import 'package:path/path.dart' as p;
+import 'package:test_api/backend.dart';
+import 'package:test_api/scaffolding.dart' show Timeout, pumpEventQueue;
+import 'package:test_api/src/backend/declarer.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
+
+import 'runner/engine.dart';
+import 'runner/plugin/environment.dart';
+import 'runner/reporter/expanded.dart';
+import 'runner/runner_suite.dart';
+import 'runner/suite.dart';
+import 'util/os.dart';
+import 'util/print_sink.dart';
+
+// Hide implementations which don't support being run directly.
+// This file is an almost direct copy of import below, but with the global
+// declarer added.
+export 'package:test_api/scaffolding.dart'
+ hide group, setUp, setUpAll, tearDown, tearDownAll, test;
+
+/// The global declarer.
+///
+/// This is used if a test file is run directly, rather than through the runner.
+Declarer? _globalDeclarer;
+
+/// Gets the declarer for the current scope.
+///
+/// When using the runner, this returns the [Zone]-scoped declarer that's set by
+/// [RemoteListener]. If the test file is run directly, this returns
+/// [_globalDeclarer] (and sets it up on the first call).
+Declarer get _declarer {
+ var declarer = Declarer.current;
+ if (declarer != null) return declarer;
+ if (_globalDeclarer != null) return _globalDeclarer!;
+
+ // Since there's no Zone-scoped declarer, the test file is being run directly.
+ // In order to run the tests, we set up our own Declarer via
+ // [_globalDeclarer], and pump the event queue as a best effort to wait for
+ // all tests to be defined before starting them.
+ _globalDeclarer = Declarer(isStandalone: true);
+
+ () async {
+ await pumpEventQueue();
+
+ var suite = RunnerSuite(
+ const PluginEnvironment(),
+ SuiteConfiguration.empty,
+ _globalDeclarer!.build(),
+ SuitePlatform(Runtime.vm, compiler: null, os: currentOSGuess),
+ path: p.prettyUri(Uri.base));
+
+ var engine = Engine();
+ engine.suiteSink.add(suite);
+ engine.suiteSink.close();
+ ExpandedReporter.watch(engine, PrintSink(),
+ color: true, printPath: false, printPlatform: false);
+
+ var success = await runZoned(() => Invoker.guard(engine.run),
+ zoneValues: {#test.declarer: _globalDeclarer});
+ if (success == true) return null;
+ print('');
+ unawaited(Future.error('Dummy exception to set exit code.'));
+ }();
+
+ return _globalDeclarer!;
+}
+
+// TODO(nweiz): This and other top-level functions should throw exceptions if
+// they're called after the declarer has finished declaring.
+/// Creates a new test case with the given description (converted to a string)
+/// and body.
+///
+/// The description will be added to the descriptions of any surrounding
+/// [group]s. If [testOn] is passed, it's parsed as a [platform selector][]; the
+/// test will only be run on matching platforms.
+///
+/// [platform selector]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-selectors
+///
+/// If [timeout] is passed, it's used to modify or replace the default timeout
+/// of 30 seconds. Timeout modifications take precedence in suite-group-test
+/// order, so [timeout] will also modify any timeouts set on the group or suite.
+///
+/// If [skip] is a String or `true`, the test is skipped. If it's a String, it
+/// should explain why the test is skipped; this reason will be printed instead
+/// of running the test. If a call to [test] is nested within a [group], a
+/// non-null `skip` parameter for the `test` will take precedence over the skip
+/// parameter in the `group`. For instance, if a `group` is set to `skip: true`,
+/// but a `test` within it is configured as `skip: false`, the `test` will not
+/// be skipped. A suite level `@Skip()` annotation cannot be overridden with
+/// `skip` arguments to `test` or `group`.
+///
+/// If [tags] is passed, it declares user-defined tags that are applied to the
+/// test. These tags can be used to select or skip the test on the command line,
+/// or to do bulk test configuration. All tags should be declared in the
+/// [package configuration file][configuring tags]. The parameter can be an
+/// [Iterable] of tag names, or a [String] representing a single tag.
+///
+/// If [retry] is passed, the test will be retried the provided number of times
+/// before being marked as a failure.
+///
+/// [configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags
+///
+/// [onPlatform] allows tests to be configured on a platform-by-platform
+/// basis. It's a map from strings that are parsed as [PlatformSelector]s to
+/// annotation classes: [Timeout], [Skip], or lists of those. These
+/// annotations apply only on the given platforms. For example:
+///
+/// test('potentially slow test', () {
+/// // ...
+/// }, onPlatform: {
+/// // This test is especially slow on Windows.
+/// 'windows': Timeout.factor(2),
+/// 'browser': [
+/// Skip('TODO: add browser support'),
+/// // This will be slow on browsers once it works on them.
+/// Timeout.factor(2)
+/// ]
+/// });
+///
+/// If multiple platforms match, the annotations apply in order as through
+/// they were in nested groups.
+///
+/// If the `solo` flag is `true`, only tests and groups marked as
+/// "solo" will be be run. This only restricts tests *within this test
+/// suite*—tests in other suites will run as normal. We recommend that users
+/// avoid this flag if possible and instead use the test runner flag `-n` to
+/// filter tests by name.
+@isTest
+void test(Object? description, dynamic Function() body,
+ {String? testOn,
+ Timeout? timeout,
+ Object? skip,
+ Object? tags,
+ Map<String, dynamic>? onPlatform,
+ int? retry,
+ // TODO(https://github.com/dart-lang/test/issues/2205): Remove deprecated.
+ @Deprecated('Debug only') @doNotSubmit bool solo = false}) {
+ _declarer.test(description.toString(), body,
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ onPlatform: onPlatform,
+ tags: tags,
+ retry: retry,
+ solo: solo);
+
+ // Force dart2js not to inline this function. We need it to be separate from
+ // `main()` in JS stack traces in order to properly determine the line and
+ // column where the test was defined. See sdk#26705.
+ return;
+ return; // ignore: dead_code
+}
+
+/// Creates a group of tests.
+///
+/// A group's description (converted to a string) is included in the descriptions
+/// of any tests or sub-groups it contains. [setUp] and [tearDown] are also scoped
+/// to the containing group.
+///
+/// If [testOn] is passed, it's parsed as a [platform selector][]; the test will
+/// only be run on matching platforms.
+///
+/// [platform selector]: https://github.com/dart-lang/test/tree/master/pkgs/test#platform-selectors
+///
+/// If [timeout] is passed, it's used to modify or replace the default timeout
+/// of 30 seconds. Timeout modifications take precedence in suite-group-test
+/// order, so [timeout] will also modify any timeouts set on the suite, and will
+/// be modified by any timeouts set on individual tests.
+///
+/// If [skip] is a String or `true`, the group is skipped. If it's a String, it
+/// should explain why the group is skipped; this reason will be printed instead
+/// of running the group's tests.
+///
+/// If [tags] is passed, it declares user-defined tags that are applied to the
+/// test. These tags can be used to select or skip the test on the command line,
+/// or to do bulk test configuration. All tags should be declared in the
+/// [package configuration file][configuring tags]. The parameter can be an
+/// [Iterable] of tag names, or a [String] representing a single tag.
+///
+/// [configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags
+///
+/// [onPlatform] allows groups to be configured on a platform-by-platform
+/// basis. It's a map from strings that are parsed as [PlatformSelector]s to
+/// annotation classes: [Timeout], [Skip], or lists of those. These
+/// annotations apply only on the given platforms. For example:
+///
+/// group('potentially slow tests', () {
+/// // ...
+/// }, onPlatform: {
+/// // These tests are especially slow on Windows.
+/// 'windows': Timeout.factor(2),
+/// 'browser': [
+/// Skip('TODO: add browser support'),
+/// // They'll be slow on browsers once it works on them.
+/// Timeout.factor(2)
+/// ]
+/// });
+///
+/// If multiple platforms match, the annotations apply in order as through
+/// they were in nested groups.
+///
+/// If the `solo` flag is `true`, only tests and groups marked as
+/// "solo" will be be run. This only restricts tests *within this test
+/// suite*—tests in other suites will run as normal. We recommend that users
+/// avoid this flag if possible, and instead use the test runner flag `-n` to
+/// filter tests by name.
+@isTestGroup
+void group(Object? description, dynamic Function() body,
+ {String? testOn,
+ Timeout? timeout,
+ Object? skip,
+ Object? tags,
+ Map<String, dynamic>? onPlatform,
+ int? retry,
+ // TODO(https://github.com/dart-lang/test/issues/2205): Remove deprecated.
+ @Deprecated('Debug only') @doNotSubmit bool solo = false}) {
+ _declarer.group(description.toString(), body,
+ testOn: testOn,
+ timeout: timeout,
+ skip: skip,
+ tags: tags,
+ onPlatform: onPlatform,
+ retry: retry,
+ solo: solo);
+
+ // Force dart2js not to inline this function. We need it to be separate from
+ // `main()` in JS stack traces in order to properly determine the line and
+ // column where the test was defined. See sdk#26705.
+ return;
+ return; // ignore: dead_code
+}
+
+/// Registers a function to be run before tests.
+///
+/// This function will be called before each test is run. [callback] may be
+/// asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, it applies only to tests in that
+/// group. [callback] will be run after any set-up callbacks in parent groups or
+/// at the top level.
+///
+/// Each callback at the top level or in a given group will be run in the order
+/// they were declared.
+void setUp(dynamic Function() callback) => _declarer.setUp(callback);
+
+/// Registers a function to be run after tests.
+///
+/// This function will be called after each test is run. [callback] may be
+/// asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, it applies only to tests in that
+/// group. [callback] will be run before any tear-down callbacks in parent
+/// groups or at the top level.
+///
+/// Each callback at the top level or in a given group will be run in the
+/// reverse of the order they were declared.
+///
+/// See also [addTearDown], which adds tear-downs to a running test.
+void tearDown(dynamic Function() callback) => _declarer.tearDown(callback);
+
+/// Registers a function to be run once before all tests.
+///
+/// [callback] may be asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, [callback] will run before all tests
+/// in that group. It will be run after any [setUpAll] callbacks in parent
+/// groups or at the top level. It won't be run if none of the tests in the
+/// group are run.
+///
+/// **Note**: This function makes it very easy to accidentally introduce hidden
+/// dependencies between tests that should be isolated. In general, you should
+/// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
+/// slow.
+void setUpAll(dynamic Function() callback) => _declarer.setUpAll(callback);
+
+/// Registers a function to be run once after all tests.
+///
+/// If this is called within a test group, [callback] will run after all tests
+/// in that group. It will be run before any [tearDownAll] callbacks in parent
+/// groups or at the top level. It won't be run if none of the tests in the
+/// group are run.
+///
+/// **Note**: This function makes it very easy to accidentally introduce hidden
+/// dependencies between tests that should be isolated. In general, you should
+/// prefer [tearDown], and only use [tearDownAll] if the callback is
+/// prohibitively slow.
+void tearDownAll(dynamic Function() callback) =>
+ _declarer.tearDownAll(callback);
diff --git a/pkgs/test_core/lib/src/util/async.dart b/pkgs/test_core/lib/src/util/async.dart
new file mode 100644
index 0000000..036d2b1
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/async.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, 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 'package:async/async.dart';
+
+/// Returns a single-subscription stream that emits the results of [operations]
+/// in the order they complete.
+///
+/// If the subscription is canceled, any pending operations are canceled as
+/// well.
+Stream<T> inCompletionOrder<T>(Iterable<CancelableOperation<T>> operations) {
+ var operationSet = operations.toSet();
+ var controller = StreamController<T>(
+ sync: true,
+ onCancel: () =>
+ Future.wait(operationSet.map((operation) => operation.cancel())));
+
+ for (var operation in operationSet) {
+ operation.value
+ .then((value) => controller.add(value))
+ .onError(controller.addError)
+ .whenComplete(() {
+ operationSet.remove(operation);
+ if (operationSet.isEmpty) controller.close();
+ });
+ }
+
+ return controller.stream;
+}
diff --git a/pkgs/test_core/lib/src/util/dart.dart b/pkgs/test_core/lib/src/util/dart.dart
new file mode 100644
index 0000000..5a1546c
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/dart.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2015, 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:convert';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:source_span/source_span.dart';
+
+import '../util/package_config.dart';
+import 'string_literal_iterator.dart';
+
+/// Runs [code] in an isolate.
+///
+/// [code] should be the contents of a Dart entrypoint. It may contain imports;
+/// they will be resolved in the same context as the host isolate. [message] is
+/// passed to the [main] method of the code being run; the caller is responsible
+/// for using this to establish communication with the isolate.
+Future<Isolate> runInIsolate(String code, Object message,
+ {SendPort? onExit}) async =>
+ Isolate.spawnUri(
+ Uri.dataFromString(code, mimeType: 'application/dart', encoding: utf8),
+ [],
+ message,
+ packageConfig: await packageConfigUri,
+ checked: true,
+ onExit: onExit);
+
+/// Takes a span whose source is the value of a string that has been parsed from
+/// a Dart file and returns the corresponding span from within that Dart file.
+///
+/// For example, suppose a Dart file contains `@Eval("1 + a")`. The
+/// [StringLiteral] `"1 + a"` is extracted; this is [context]. Its contents are
+/// then parsed, producing an error pointing to [span]:
+///
+/// line 1, column 5:
+/// 1 + a
+/// ^
+///
+/// This span isn't very useful, since it only shows the location within the
+/// [StringLiteral]'s value. So it's passed to [contextualizeSpan] along with
+/// [context] and [file] (which contains the source of the entire Dart file),
+/// which then returns:
+///
+/// line 4, column 12 of file.dart:
+/// @Eval("1 + a")
+/// ^
+///
+/// This properly handles multiline literals, adjacent literals, and literals
+/// containing escape sequences. It does not support interpolated literals.
+///
+/// This will return `null` if [context] contains an invalid string or does not
+/// contain [span].
+SourceSpan? contextualizeSpan(
+ SourceSpan span, StringLiteral context, SourceFile file) {
+ var contextRunes = StringLiteralIterator(context)..moveNext();
+
+ for (var i = 0; i < span.start.offset; i++) {
+ if (!contextRunes.moveNext()) return null;
+ }
+
+ var start = contextRunes.offset;
+ for (var spanRune in span.text.runes) {
+ if (spanRune != contextRunes.current) return null;
+ contextRunes.moveNext();
+ }
+
+ return file.span(start, contextRunes.offset);
+}
+
+/// Parses and returns the currently enabled experiments from
+/// [Platform.executableArguments].
+final List<String> enabledExperiments = () {
+ var experiments = <String>[];
+ var itr = Platform.executableArguments.iterator;
+ while (itr.moveNext()) {
+ var arg = itr.current;
+ if (arg == '--enable-experiment') {
+ if (!itr.moveNext()) break;
+ experiments.add(itr.current);
+ } else if (arg.startsWith('--enable-experiment=')) {
+ var parts = arg.split('=');
+ if (parts.length == 2) {
+ experiments.addAll(parts[1].split(','));
+ }
+ }
+ }
+ return experiments;
+}();
diff --git a/pkgs/test_core/lib/src/util/detaching_future.dart b/pkgs/test_core/lib/src/util/detaching_future.dart
new file mode 100644
index 0000000..594f313
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/detaching_future.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2020, 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.
+
+/// A handle on a [Future] that detaches from the original evaluation context
+/// after it has resolved.
+///
+/// A [Future] holds the Zone it was created in and may hold references to
+/// `async` functions that `await` it. When a future is stored in a static
+/// variable it won't be garbage collected which means all zone variables and
+/// variables in async functions get leaked. [DetachingFuture] works around this
+/// by forgetting the original [Future] after it has resolved, and wrapping the
+/// resolved value with `Future.value` for later calls.
+///
+/// https://github.com/dart-lang/sdk/issues/42457
+/// https://github.com/dart-lang/sdk/issues/42458
+///
+/// In the case of a future that resolves to an error the original future is
+/// retained.
+class DetachingFuture<T> {
+ late T _value;
+ Future<T>? _inProgress;
+
+ DetachingFuture(Future<T> inProgress) : _inProgress = inProgress {
+ inProgress.then((result) {
+ _value = result;
+ _inProgress = null;
+ });
+ }
+
+ Future<T> get asFuture => _inProgress ?? Future.value(_value);
+}
diff --git a/pkgs/test_core/lib/src/util/errors.dart b/pkgs/test_core/lib/src/util/errors.dart
new file mode 100644
index 0000000..7ebea6e
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/errors.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, 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.
+
+/// A regular expression to match the exception prefix that some exceptions'
+/// [Object.toString] values contain.
+final _exceptionPrefix = RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): ');
+
+/// Get a string description of an exception.
+///
+/// Many exceptions include the exception class name at the beginning of their
+/// [toString], so we remove that if it exists.
+String getErrorMessage(Object error) =>
+ error.toString().replaceFirst(_exceptionPrefix, '');
diff --git a/pkgs/test_core/lib/src/util/exit_codes.dart b/pkgs/test_core/lib/src/util/exit_codes.dart
new file mode 100644
index 0000000..7c8b2db
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/exit_codes.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2015, 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.
+
+/// Exit code constants.
+///
+/// From [the BSD sysexits manpage][manpage]. Not every constant here is used.
+///
+/// [manpage]: http://www.freebsd.org/cgi/man.cgi?query=sysexits
+/// The command completely successfully.
+const success = 0;
+
+/// The command was used incorrectly.
+const usage = 64;
+
+/// The input data was incorrect.
+const data = 65;
+
+/// An input file did not exist or was unreadable.
+const noInput = 66;
+
+/// The user specified did not exist.
+const noUser = 67;
+
+/// The host specified did not exist.
+const noHost = 68;
+
+/// A service is unavailable.
+const unavailable = 69;
+
+/// An internal software error has been detected.
+const software = 70;
+
+/// An operating system error has been detected.
+const os = 71;
+
+/// Some system file did not exist or was unreadable.
+const osFile = 72;
+
+/// A user-specified output file cannot be created.
+const cantCreate = 73;
+
+/// An error occurred while doing I/O on some file.
+const io = 74;
+
+/// Temporary failure, indicating something that is not really an error.
+const tempFail = 75;
+
+/// The remote system returned something invalid during a protocol exchange.
+const protocol = 76;
+
+/// The user did not have sufficient permissions.
+const noPerm = 77;
+
+/// Something was unconfigured or mis-configured.
+const config = 78;
+
+/// No tests were ran.
+const noTestsRan = 79;
diff --git a/pkgs/test_core/lib/src/util/io.dart b/pkgs/test_core/lib/src/util/io.dart
new file mode 100644
index 0000000..a082dd3
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/io.dart
@@ -0,0 +1,267 @@
+// Copyright (c) 2015, 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:core' as core;
+import 'dart:core';
+import 'dart:io';
+import 'dart:math';
+
+import 'package:async/async.dart';
+import 'package:path/path.dart' as p;
+import 'package:test_api/src/backend/compiler.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/operating_system.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+
+import 'pretty_print.dart';
+
+/// The default line length for output when there isn't a terminal attached to
+/// stdout.
+const _defaultLineLength = 200;
+
+/// Whether the test runner is running on Google-internal infrastructure.
+final bool inGoogle = Platform.version.contains('(google3)');
+
+/// The maximum line length for output.
+final int lineLength = () {
+ try {
+ return stdout.terminalColumns;
+ } on UnsupportedError {
+ // This can throw an [UnsupportedError] if we're running in a JS context
+ // where `dart:io` is unavailable.
+ return _defaultLineLength;
+ } on StdoutException {
+ return _defaultLineLength;
+ }
+}();
+
+/// The root directory of the Dart SDK.
+final String sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));
+
+/// The current operating system.
+final currentOS = OperatingSystem.findByIoName(Platform.operatingSystem);
+
+/// Returns a [SuitePlatform] with the given [runtime], and with
+/// [SuitePlatform.os] and [inGoogle] determined automatically.
+///
+/// If [runtime] is a browser, this will set [SuitePlatform.os] to
+/// [OperatingSystem.none].
+// TODO: https://github.com/dart-lang/test/issues/2119 - require compiler
+SuitePlatform currentPlatform(Runtime runtime, [Compiler? compiler]) =>
+ SuitePlatform(runtime,
+ compiler: compiler,
+ os: runtime.isBrowser ? OperatingSystem.none : currentOS,
+ inGoogle: inGoogle);
+
+/// A transformer that decodes bytes using UTF-8 and splits them on newlines.
+final lineSplitter = StreamTransformer<List<int>, String>(
+ (stream, cancelOnError) => utf8.decoder
+ .bind(stream)
+ .transform(const LineSplitter())
+ .listen(null, cancelOnError: cancelOnError));
+
+/// A queue of lines of standard input.
+///
+/// Also returns an empty stream for Fuchsia since Fuchsia components can't
+/// access stdin.
+StreamQueue<String> get stdinLines =>
+ _stdinLines ??= StreamQueue(Platform.isFuchsia
+ ? const Stream<String>.empty()
+ : lineSplitter.bind(stdin));
+
+StreamQueue<String>? _stdinLines;
+
+/// Call cancel on [stdinLines], but only if it's been accessed previously.
+void cancelStdinLines() => _stdinLines?.cancel(immediate: true);
+
+/// Whether this is being run as a subprocess in the test package's own tests.
+bool inTestTests = Platform.environment['_DART_TEST_TESTING'] == 'true';
+
+/// The root directory below which to nest temporary directories created by the
+/// test runner.
+///
+/// This is configurable so that the test code can validate that the runner
+/// cleans up after itself fully.
+final _tempDir = Platform.environment.containsKey('_UNITTEST_TEMP_DIR')
+ ? Platform.environment['_UNITTEST_TEMP_DIR']!
+ : Directory.systemTemp.path;
+
+/// Whether or not the current terminal supports ansi escape codes.
+///
+/// Otherwise only printable ASCII characters should be used.
+bool get canUseSpecialChars =>
+ (!Platform.isWindows || stdout.supportsAnsiEscapes) && !inTestTests;
+
+/// Detect whether we're running in a Github Actions context.
+///
+/// See
+/// https://docs.github.com/en/actions/learn-github-actions/environment-variables.
+bool get inGithubContext => Platform.environment['GITHUB_ACTIONS'] == 'true';
+
+/// Creates a temporary directory and returns its path.
+String createTempDir() =>
+ Directory(_tempDir).createTempSync('dart_test_').resolveSymbolicLinksSync();
+
+/// Creates a temporary directory and passes its path to [fn].
+///
+/// Once the [Future] returned by [fn] completes, the temporary directory and
+/// all its contents are deleted. [fn] can also return `null`, in which case
+/// the temporary directory is deleted immediately afterwards.
+///
+/// Returns a future that completes to the value that the future returned from
+/// [fn] completes to.
+Future withTempDir(Future Function(String) fn) {
+ return Future.sync(() {
+ var tempDir = createTempDir();
+ return Future.sync(() => fn(tempDir))
+ .whenComplete(() => Directory(tempDir).deleteWithRetry());
+ });
+}
+
+/// Wraps [text] so that it fits within [lineLength].
+///
+/// This preserves existing newlines and doesn't consider terminal color escapes
+/// part of a word's length. It only splits words on spaces, not on other sorts
+/// of whitespace.
+String wordWrap(String text) {
+ return text.split('\n').map((originalLine) {
+ var buffer = StringBuffer();
+ var lengthSoFar = 0;
+ for (var word in originalLine.split(' ')) {
+ var wordLength = withoutColors(word).length;
+ if (wordLength > lineLength) {
+ if (lengthSoFar != 0) buffer.writeln();
+ buffer.writeln(word);
+ } else if (lengthSoFar == 0) {
+ buffer.write(word);
+ lengthSoFar = wordLength;
+ } else if (lengthSoFar + 1 + wordLength > lineLength) {
+ buffer.writeln();
+ buffer.write(word);
+ lengthSoFar = wordLength;
+ } else {
+ buffer.write(' $word');
+ lengthSoFar += 1 + wordLength;
+ }
+ }
+ return buffer.toString();
+ }).join('\n');
+}
+
+/// Print a warning containing [message].
+///
+/// This automatically wraps lines if they get too long. If [color] is passed,
+/// it controls whether the warning header is color; otherwise, it defaults to
+/// [canUseSpecialChars].
+///
+/// If [print] is `true`, this prints the message using [print] to associate it
+/// with the current test. Otherwise, it prints it using [stderr].
+void warn(String message, {bool? color, bool print = false}) {
+ color ??= canUseSpecialChars;
+ var header = color ? '\u001b[33mWarning:\u001b[0m' : 'Warning:';
+ (print ? core.print : stderr.writeln)(wordWrap('$header $message\n'));
+}
+
+/// Repeatedly finds a probably-unused port on localhost and passes it to
+/// [tryPort] until it binds successfully.
+///
+/// [tryPort] should return a non-`null` value or a Future completing to a
+/// non-`null` value once it binds successfully. This value will be returned
+/// by [getUnusedPort] in turn.
+///
+/// This is necessary for ensuring that our port binding isn't flaky for
+/// applications that don't print out the bound port.
+Future<T> getUnusedPort<T extends Object>(
+ FutureOr<T> Function(int port) tryPort) async {
+ T? value;
+ await Future.doWhile(() async {
+ value = await tryPort(await getUnsafeUnusedPort());
+ return value == null;
+ });
+ return value!;
+}
+
+/// Whether this computer supports binding to IPv6 addresses.
+var _maySupportIPv6 = true;
+
+/// Returns a port that is probably, but not definitely, not in use.
+///
+/// This has a built-in race condition: another process may bind this port at
+/// any time after this call has returned. If at all possible, callers should
+/// use [getUnusedPort] instead.
+Future<int> getUnsafeUnusedPort() async {
+ late int port;
+ if (_maySupportIPv6) {
+ try {
+ final socket = await ServerSocket.bind(InternetAddress.loopbackIPv6, 0,
+ v6Only: true);
+ port = socket.port;
+ await socket.close();
+ } on SocketException {
+ _maySupportIPv6 = false;
+ }
+ }
+ if (!_maySupportIPv6) {
+ final socket = await RawServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+ port = socket.port;
+ await socket.close();
+ }
+ return port;
+}
+
+/// Returns the full URL of the Chrome remote debugger for the main page.
+///
+/// This takes the [base] remote debugger URL (which points to a browser-wide
+/// page) and uses its JSON API to find the resolved URL for debugging the host
+/// page.
+Future<Uri> getRemoteDebuggerUrl(Uri base) async {
+ try {
+ var client = HttpClient();
+ var request = await client.getUrl(base.resolve('/json/list'));
+ var response = await request.close();
+ var jsonObject =
+ await json.fuse(utf8).decoder.bind(response).single as List;
+ return base
+ .resolve((jsonObject.first as Map)['devtoolsFrontendUrl'] as String);
+ } catch (_) {
+ // If we fail to talk to the remote debugger protocol, give up and return
+ // the raw URL rather than crashing.
+ return base;
+ }
+}
+
+extension RetryDelete on FileSystemEntity {
+ Future<void> deleteWithRetry() async {
+ var attempt = 0;
+ while (true) {
+ try {
+ await delete(recursive: true);
+ return;
+ } on FileSystemException {
+ if (attempt == 2) rethrow;
+ attempt++;
+ await Future<void>.delayed(
+ Duration(milliseconds: pow(10, attempt).toInt()));
+ }
+ }
+ }
+}
+
+extension WindowsFilePaths on String {
+ /// Strip out the leading slash before the drive letter on windows.
+ ///
+ /// In some windows environments full paths get passed with `/` before the
+ /// drive letter. Normalize paths to exclude this slash when it exists.
+ String get stripDriveLetterLeadingSlash {
+ if (Platform.isWindows &&
+ startsWith('/') &&
+ length >= 3 &&
+ this[2] == ':') {
+ return substring(1);
+ }
+ return this;
+ }
+}
diff --git a/pkgs/test_core/lib/src/util/io_stub.dart b/pkgs/test_core/lib/src/util/io_stub.dart
new file mode 100644
index 0000000..7953877
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/io_stub.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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 'package:test_api/src/backend/compiler.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+
+SuitePlatform currentPlatform(Runtime runtime, Compiler? compiler) =>
+ throw UnsupportedError(
+ 'Getting the current platform is only supported where dart:io exists');
diff --git a/pkgs/test_core/lib/src/util/os.dart b/pkgs/test_core/lib/src/util/os.dart
new file mode 100644
index 0000000..3db1608
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/os.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, 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 'package:path/path.dart' as p;
+import 'package:test_api/src/backend/operating_system.dart'; // ignore: implementation_imports
+
+/// Directories that are specific to OS X.
+///
+/// This is used to try to distinguish OS X and Linux in [currentOSGuess].
+final _macOSDirectories = {
+ '/Applications',
+ '/Library',
+ '/Network',
+ '/System',
+ '/Users',
+};
+
+/// Returns the best guess for the current operating system without using
+/// `dart:io`.
+///
+/// This is useful for running test files directly and skipping tests as
+/// appropriate. The only OS-specific information we have is the current path,
+/// which we try to use to figure out the OS.
+final OperatingSystem currentOSGuess = (() {
+ if (p.style == p.Style.url) return OperatingSystem.none;
+ if (p.style == p.Style.windows) return OperatingSystem.windows;
+ if (_macOSDirectories.any(p.current.startsWith)) return OperatingSystem.macOS;
+ return OperatingSystem.linux;
+})();
diff --git a/pkgs/test_core/lib/src/util/package_config.dart b/pkgs/test_core/lib/src/util/package_config.dart
new file mode 100644
index 0000000..c33dda6
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/package_config.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, 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:io';
+import 'dart:isolate';
+
+import 'package:package_config/package_config.dart';
+import 'package:path/path.dart' as p;
+
+/// The [PackageConfig] parsed from the current isolates package config file.
+final Future<PackageConfig> currentPackageConfig = () async {
+ return loadPackageConfigUri(await packageConfigUri);
+}();
+
+final Future<Uri> packageConfigUri = () async {
+ var uri = await Isolate.packageConfig;
+ if (uri == null) {
+ throw StateError('Unable to find a package config');
+ }
+ return uri;
+}();
+
+final _originalWorkingDirectory = Directory.current.uri;
+
+/// Returns an `package:` URI for [path] if it is in a package, otherwise
+/// returns an absolute file URI.
+Future<Uri> absoluteUri(String path) async {
+ final uri = p.toUri(path);
+ final absoluteUri =
+ uri.isAbsolute ? uri : _originalWorkingDirectory.resolveUri(uri);
+ try {
+ final packageConfig = await currentPackageConfig;
+ return packageConfig.toPackageUri(absoluteUri) ?? absoluteUri;
+ } on StateError {
+ // Workaround for a missing package config.
+ return absoluteUri;
+ }
+}
diff --git a/pkgs/test_core/lib/src/util/pretty_print.dart b/pkgs/test_core/lib/src/util/pretty_print.dart
new file mode 100644
index 0000000..f425629
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/pretty_print.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, 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.
+
+/// A regular expression matching terminal color codes.
+final _colorCode = RegExp('\u001b\\[[0-9;]+m');
+
+/// Returns [str] without any color codes.
+String withoutColors(String str) => str.replaceAll(_colorCode, '');
+
+/// A regular expression matching a single vowel.
+final _vowel = RegExp('[aeiou]');
+
+/// Returns [noun] with an indefinite article ("a" or "an") added, based on
+/// whether its first letter is a vowel.
+String a(String noun) => noun.startsWith(_vowel) ? 'an $noun' : 'a $noun';
+
+/// Indent each line in [string] by 2 spaces.
+String indent(String text) {
+ var lines = text.split('\n');
+ if (lines.length == 1) return ' $text';
+
+ var buffer = StringBuffer();
+
+ for (var line in lines.take(lines.length - 1)) {
+ buffer.writeln(' $line');
+ }
+ buffer.write(' ${lines.last}');
+ return buffer.toString();
+}
+
+/// Truncates [text] to fit within [maxLength].
+///
+/// This will try to truncate along word boundaries and preserve words both at
+/// the beginning and the end of [text].
+String truncate(String text, int maxLength) {
+ // Return the full message if it fits.
+ if (text.length <= maxLength) return text;
+
+ // If we can fit the first and last three words, do so.
+ var words = text.split(' ');
+ if (words.length > 1) {
+ var i = words.length;
+ var length = words.first.length + 4;
+ do {
+ i--;
+ length += 1 + words[i].length;
+ } while (length <= maxLength && i > 0);
+ if (length > maxLength || i == 0) i++;
+ if (i < words.length - 4) {
+ // Require at least 3 words at the end.
+ var buffer = StringBuffer();
+ buffer.write(words.first);
+ buffer.write(' ...');
+ for (; i < words.length; i++) {
+ buffer.write(' ');
+ buffer.write(words[i]);
+ }
+ return buffer.toString();
+ }
+ }
+
+ // Otherwise truncate to return the trailing text, but attempt to start at
+ // the beginning of a word.
+ var result = text.substring(text.length - maxLength + 4);
+ var firstSpace = result.indexOf(' ');
+ if (firstSpace > 0) {
+ result = result.substring(firstSpace);
+ }
+ return '...$result';
+}
diff --git a/pkgs/test_core/lib/src/util/print_sink.dart b/pkgs/test_core/lib/src/util/print_sink.dart
new file mode 100644
index 0000000..ebb2fe8
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/print_sink.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2019, 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.
+
+class PrintSink implements StringSink {
+ final _buffer = StringBuffer();
+
+ @override
+ void write(Object? obj) {
+ _buffer.write(obj);
+ _flush();
+ }
+
+ @override
+ void writeAll(Iterable objects, [String separator = '']) {
+ _buffer.writeAll(objects, separator);
+ _flush();
+ }
+
+ @override
+ void writeCharCode(int charCode) {
+ _buffer.writeCharCode(charCode);
+ _flush();
+ }
+
+ @override
+ void writeln([Object? obj = '']) {
+ _buffer.writeln(obj ?? '');
+ _flush();
+ }
+
+ /// [print] if the content available ends with a newline.
+ void _flush() {
+ if ('$_buffer'.endsWith('\n')) {
+ print(_buffer);
+ _buffer.clear();
+ }
+ }
+}
diff --git a/pkgs/test_core/lib/src/util/stack_trace_mapper.dart b/pkgs/test_core/lib/src/util/stack_trace_mapper.dart
new file mode 100644
index 0000000..9d4248d
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/stack_trace_mapper.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2015, 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 'package:source_map_stack_trace/source_map_stack_trace.dart' as mapper;
+import 'package:source_maps/source_maps.dart';
+import 'package:test_api/backend.dart' show StackTraceMapper;
+
+/// A class for mapping JS stack traces to Dart stack traces using source maps.
+class JSStackTraceMapper implements StackTraceMapper {
+ /// The parsed source map.
+ ///
+ /// This is initialized lazily in `mapStackTrace()`.
+ Mapping? _mapping;
+
+ /// The same package resolution information as was passed to dart2js.
+ final Map<String, Uri>? _packageMap;
+
+ /// The URL of the SDK root from which dart2js loaded its sources.
+ final Uri? _sdkRoot;
+
+ /// The contents of the source map.
+ final String _mapContents;
+
+ /// The URL of the source map.
+ final Uri? _mapUrl;
+
+ JSStackTraceMapper(this._mapContents,
+ {Uri? mapUrl, Map<String, Uri>? packageMap, Uri? sdkRoot})
+ : _mapUrl = mapUrl,
+ _packageMap = packageMap,
+ _sdkRoot = sdkRoot;
+
+ /// Converts [trace] into a Dart stack trace.
+ @override
+ StackTrace mapStackTrace(StackTrace trace) {
+ var mapping = _mapping ??= parseExtended(_mapContents, mapUrl: _mapUrl);
+ return mapper.mapStackTrace(mapping, trace,
+ packageMap: _packageMap, sdkRoot: _sdkRoot);
+ }
+
+ /// Returns a Map representation which is suitable for JSON serialization.
+ @override
+ Map<String, dynamic> serialize() {
+ return {
+ 'mapContents': _mapContents,
+ 'sdkRoot': _sdkRoot?.toString(),
+ 'packageConfigMap': _serializePackageConfigMap(_packageMap),
+ 'mapUrl': _mapUrl?.toString(),
+ };
+ }
+
+ /// Returns a [StackTraceMapper] contained in the provided serialized
+ /// representation.
+ static StackTraceMapper? deserialize(Map? serialized) {
+ if (serialized == null) return null;
+ var deserialized = _deserializePackageConfigMap(
+ (serialized['packageConfigMap'] as Map).cast<String, String>());
+
+ return JSStackTraceMapper(serialized['mapContents'] as String,
+ sdkRoot: Uri.parse(serialized['sdkRoot'] as String),
+ packageMap: deserialized,
+ mapUrl: Uri.parse(serialized['mapUrl'] as String));
+ }
+
+ /// Converts a [packageConfigMap] into a format suitable for JSON
+ /// serialization.
+ static Map<String, String>? _serializePackageConfigMap(
+ Map<String, Uri>? packageConfigMap) {
+ if (packageConfigMap == null) return null;
+ return packageConfigMap.map((key, value) => MapEntry(key, '$value'));
+ }
+
+ /// Converts a serialized package config map into a format suitable for
+ /// the [PackageResolver]
+ static Map<String, Uri>? _deserializePackageConfigMap(
+ Map<String, String>? serialized) {
+ if (serialized == null) return null;
+ return serialized.map((key, value) => MapEntry(key, Uri.parse(value)));
+ }
+}
diff --git a/pkgs/test_core/lib/src/util/string_literal_iterator.dart b/pkgs/test_core/lib/src/util/string_literal_iterator.dart
new file mode 100644
index 0000000..d58f64a
--- /dev/null
+++ b/pkgs/test_core/lib/src/util/string_literal_iterator.dart
@@ -0,0 +1,245 @@
+// Copyright (c) 2015, 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:collection';
+
+import 'package:analyzer/dart/ast/ast.dart';
+
+// ASCII character codes.
+
+const _zero = 0x30;
+const _nine = 0x39;
+const _backslash = 0x5C;
+const _openCurly = 0x7B;
+const _closeCurly = 0x7D;
+const _capitalA = 0x41;
+const _capitalZ = 0x5A;
+const _a = 0x61;
+const _n = 0x6E;
+const _r = 0x72;
+const _f = 0x66;
+const _b = 0x62;
+const _t = 0x74;
+const _u = 0x75;
+const _v = 0x76;
+const _x = 0x78;
+const _z = 0x7A;
+const _newline = 0xA;
+const _carriageReturn = 0xD;
+const _formFeed = 0xC;
+const _backspace = 0x8;
+const _tab = 0x9;
+const _verticalTab = 0xB;
+
+/// An iterator over the runes in the value of a [StringLiteral].
+///
+/// In addition to exposing the values of the runes themselves, this also
+/// exposes the offset of the current rune in the Dart source file.
+class StringLiteralIterator implements Iterator<int> {
+ @override
+ int get current => _current!;
+ int? _current;
+
+ /// The offset of the beginning of [current] in the Dart source file that
+ /// contains the string literal.
+ ///
+ /// Before iteration begins, this points to the character before the first
+ /// rune.
+ int get offset => _offset;
+ late int _offset;
+
+ /// The offset of the next rune.
+ ///
+ /// This isn't necessarily just `offset + 1`, since a single rune may be
+ /// represented by multiple characters in the source file, or a string literal
+ /// may be composed of several adjacent string literals.
+ int? _nextOffset;
+
+ /// All [SimpleStringLiteral]s that compose the input literal.
+ ///
+ /// If the input literal is itself a [SimpleStringLiteral], this just contains
+ /// that literal; otherwise, the literal is an [AdjacentStrings], and this
+ /// contains its component literals.
+ final _strings = Queue<SimpleStringLiteral>();
+
+ /// Whether this is a raw string that begins with `r`.
+ ///
+ /// This is necessary for knowing how to parse escape sequences.
+ bool? _isRaw;
+
+ /// The iterator over the runes in the Dart source file.
+ ///
+ /// When switching to a new string in [_strings], this is updated to point to
+ /// that string's component runes.
+ Iterator<int>? _runes;
+
+ /// The result of the last call to `_runes.moveNext`.
+ bool _runesHasCurrent = false;
+
+ /// Creates a new [StringLiteralIterator] iterating over the contents of
+ /// [literal].
+ ///
+ /// Throws an [ArgumentError] if [literal] contains interpolated strings.
+ StringLiteralIterator(StringLiteral literal) {
+ if (literal is StringInterpolation) {
+ throw ArgumentError("Can't iterate over an interpolated string.");
+ } else if (literal is SimpleStringLiteral) {
+ _strings.add(literal);
+ } else {
+ assert(literal is AdjacentStrings);
+
+ for (var string in (literal as AdjacentStrings).strings) {
+ if (string is StringInterpolation) {
+ throw ArgumentError("Can't iterate over an interpolated string.");
+ }
+ _strings.add(string as SimpleStringLiteral);
+ }
+ }
+
+ _offset = _strings.first.contentsOffset - 1;
+ }
+
+ @override
+ bool moveNext() {
+ // If we're at beginning of a [SimpleStringLiteral], move forward until
+ // there's actually text to consume.
+ while (_runes == null || !_runesHasCurrent) {
+ if (_strings.isEmpty) {
+ // Move the offset past the end of the text.
+ _offset = _nextOffset!;
+ _current = null;
+ return false;
+ }
+
+ var string = _strings.removeFirst();
+ var start = string.contentsOffset - string.offset;
+
+ // Compensate for the opening and closing quotes.
+ var end = start +
+ string.literal.lexeme.length -
+ 2 * (string.isMultiline ? 3 : 1) -
+ (string.isRaw ? 1 : 0);
+ var text = string.literal.lexeme.substring(start, end);
+
+ _nextOffset = string.contentsOffset;
+ _isRaw = string.isRaw;
+ _runes = text.runes.iterator;
+ _runesHasCurrent = _runes!.moveNext();
+ }
+
+ _offset = _nextOffset!;
+ _current = _nextRune();
+ if (_current != null) return true;
+
+ // If we encounter a parse failure, stop moving forward immediately.
+ _strings.clear();
+ return false;
+ }
+
+ /// Consume and return the next rune.
+ int? _nextRune() {
+ if (_isRaw! || _runes!.current != _backslash) {
+ var rune = _runes!.current;
+ _moveRunesNext();
+ return (rune < 0) ? null : rune;
+ }
+
+ if (!_moveRunesNext()) return null;
+ return _parseEscapeSequence();
+ }
+
+ /// Parse an escape sequence in the underlying Dart text.
+ ///
+ /// This assumes that a backslash has already been consumed. It leaves the
+ /// [_runes] cursor on the first character after the escape sequence.
+ int? _parseEscapeSequence() {
+ switch (_runes!.current) {
+ case _n:
+ _moveRunesNext();
+ return _newline;
+ case _r:
+ _moveRunesNext();
+ return _carriageReturn;
+ case _f:
+ _moveRunesNext();
+ return _formFeed;
+ case _b:
+ _moveRunesNext();
+ return _backspace;
+ case _t:
+ _moveRunesNext();
+ return _tab;
+ case _v:
+ _moveRunesNext();
+ return _verticalTab;
+ case _x:
+ if (!_moveRunesNext()) return null;
+ return _parseHex(2);
+ case _u:
+ if (!_moveRunesNext()) return null;
+ if (_runes!.current != _openCurly) return _parseHex(4);
+ if (!_moveRunesNext()) return null;
+
+ var number = _parseHexSequence();
+ if (_runes!.current != _closeCurly) return null;
+ if (!_moveRunesNext()) return null;
+ return number;
+ default:
+ var rune = _runes!.current;
+ _moveRunesNext();
+ return rune;
+ }
+ }
+
+ /// Parse a variable-length sequence of hexadecimal digits and returns their
+ /// value as an [int].
+ ///
+ /// This parses digits as they appear in a unicode escape sequence: one to six
+ /// hex digits.
+ int? _parseHexSequence() {
+ var number = _parseHexDigit(_runes!.current);
+ if (number == null) return null;
+ if (!_moveRunesNext()) return null;
+
+ for (var i = 0; i < 5; i++) {
+ var digit = _parseHexDigit(_runes!.current);
+ if (digit == null) break;
+ number = number! * 16 + digit;
+ if (!_moveRunesNext()) return null;
+ }
+
+ return number;
+ }
+
+ /// Parses [digits] hexadecimal digits and returns their value as an [int].
+ int? _parseHex(int digits) {
+ var number = 0;
+ for (var i = 0; i < digits; i++) {
+ if (_runes!.current == -1) return null;
+ var digit = _parseHexDigit(_runes!.current);
+ if (digit == null) return null;
+ number = number * 16 + digit;
+ _moveRunesNext();
+ }
+ return number;
+ }
+
+ /// Parses a single hexadecimal digit.
+ int? _parseHexDigit(int rune) {
+ if (rune < _zero) return null;
+ if (rune <= _nine) return rune - _zero;
+ if (rune < _capitalA) return null;
+ if (rune <= _capitalZ) return 10 + rune - _capitalA;
+ if (rune < _a) return null;
+ if (rune <= _z) return 10 + rune - _a;
+ return null;
+ }
+
+ /// Move [_runes] to the next rune and update [_nextOffset].
+ bool _moveRunesNext() {
+ var result = _runesHasCurrent = _runes!.moveNext();
+ _nextOffset = _nextOffset! + 1;
+ return result;
+ }
+}
diff --git a/pkgs/test_core/lib/test_core.dart b/pkgs/test_core/lib/test_core.dart
new file mode 100644
index 0000000..b1df09d
--- /dev/null
+++ b/pkgs/test_core/lib/test_core.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2013, 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.
+
+@Deprecated('package:test_core is not intended for general use. '
+ 'Please use package:test.')
+library;
+
+export 'package:test_api/hooks.dart' show TestFailure;
+
+export 'scaffolding.dart';
diff --git a/pkgs/test_core/mono_pkg.yaml b/pkgs/test_core/mono_pkg.yaml
new file mode 100644
index 0000000..9ef7927
--- /dev/null
+++ b/pkgs/test_core/mono_pkg.yaml
@@ -0,0 +1,17 @@
+# See https://pub.dev/packages/mono_repo
+
+sdk:
+- dev
+
+stages:
+- analyze_and_format:
+ - group:
+ - format
+ - analyze: --fatal-infos
+ - group:
+ - analyze
+ sdk: pubspec
+- unit_test:
+ - group:
+ - command: dart test
+ sdk: [dev, pubspec]
diff --git a/pkgs/test_core/pubspec.yaml b/pkgs/test_core/pubspec.yaml
new file mode 100644
index 0000000..d08e24c
--- /dev/null
+++ b/pkgs/test_core/pubspec.yaml
@@ -0,0 +1,35 @@
+name: test_core
+version: 0.6.8
+description: A basic library for writing tests and running them on the VM.
+repository: https://github.com/dart-lang/test/tree/master/pkgs/test_core
+resolution: workspace
+
+environment:
+ sdk: ^3.5.0
+
+dependencies:
+ analyzer: '>=6.0.0 <8.0.0'
+ args: ^2.0.0
+ async: ^2.5.0
+ boolean_selector: ^2.1.0
+ collection: ^1.15.0
+ coverage: ^1.0.0
+ frontend_server_client: '>=3.2.0 <5.0.0'
+ glob: ^2.0.0
+ io: ^1.0.0
+ meta: ^1.3.0
+ package_config: ^2.0.0
+ path: ^1.8.0
+ pool: ^1.5.0
+ source_map_stack_trace: ^2.1.0
+ source_maps: ^0.10.10
+ source_span: ^1.8.0
+ stack_trace: ^1.10.0
+ stream_channel: ^2.1.0
+ # Use an exact version until the test_api package is stable.
+ test_api: 0.7.4
+ vm_service: ">=6.0.0 <16.0.0"
+ yaml: ^3.0.0
+
+dev_dependencies:
+ test: any
diff --git a/pkgs/test_core/test/runner/vm/test_compiler_test.dart b/pkgs/test_core/test/runner/vm/test_compiler_test.dart
new file mode 100644
index 0000000..a6e50ef
--- /dev/null
+++ b/pkgs/test_core/test/runner/vm/test_compiler_test.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2024, 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:convert';
+
+import 'package:test/test.dart';
+import 'package:test_core/src/runner/vm/test_compiler.dart';
+
+void main() {
+ group('VM test templates', () {
+ test('include package config URI variable', () async {
+ // This variable is read through the VM service and should not be removed.
+ final template = testBootstrapContents(
+ testUri: Uri.file('foo.dart'),
+ languageVersionComment: '// version comment',
+ packageConfigUri: Uri.file('package_config.json'),
+ testType: VmTestType.isolate,
+ );
+ final lines = LineSplitter.split(template).map((line) => line.trim());
+ expect(lines,
+ contains("const packageConfigLocation = 'package_config.json';"));
+ });
+ });
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..2e08374
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,14 @@
+name: test_workspace
+publish_to: none
+environment:
+ sdk: ^3.5.0
+workspace:
+ - integration_tests/regression
+ - integration_tests/spawn_hybrid
+ - integration_tests/wasm
+ - pkgs/checks
+ - pkgs/test
+ - pkgs/test_api
+ - pkgs/test_core
+dev_dependencies:
+ dart_flutter_team_lints: ^3.1.0
diff --git a/tool/ci.sh b/tool/ci.sh
new file mode 100755
index 0000000..040c70d
--- /dev/null
+++ b/tool/ci.sh
@@ -0,0 +1,167 @@
+#!/bin/bash
+# Created with package:mono_repo v6.6.2
+
+# Support built in commands on windows out of the box.
+
+# When it is a flutter repo (check the pubspec.yaml for "sdk: flutter")
+# then "flutter pub" is called instead of "dart pub".
+# This assumes that the Flutter SDK has been installed in a previous step.
+function pub() {
+ if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then
+ command flutter pub "$@"
+ else
+ command dart pub "$@"
+ fi
+}
+
+function format() {
+ command dart format "$@"
+}
+
+# When it is a flutter repo (check the pubspec.yaml for "sdk: flutter")
+# then "flutter analyze" is called instead of "dart analyze".
+# This assumes that the Flutter SDK has been installed in a previous step.
+function analyze() {
+ if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then
+ command flutter analyze "$@"
+ else
+ command dart analyze "$@"
+ fi
+}
+
+if [[ -z ${PKGS} ]]; then
+ echo -e '\033[31mPKGS environment variable must be set! - TERMINATING JOB\033[0m'
+ exit 64
+fi
+
+if [[ "$#" == "0" ]]; then
+ echo -e '\033[31mAt least one task argument must be provided! - TERMINATING JOB\033[0m'
+ exit 64
+fi
+
+SUCCESS_COUNT=0
+declare -a FAILURES
+
+for PKG in ${PKGS}; do
+ echo -e "\033[1mPKG: ${PKG}\033[22m"
+ EXIT_CODE=0
+ pushd "${PKG}" >/dev/null || EXIT_CODE=$?
+
+ if [[ ${EXIT_CODE} -ne 0 ]]; then
+ echo -e "\033[31mPKG: '${PKG}' does not exist - TERMINATING JOB\033[0m"
+ exit 64
+ fi
+
+ dart pub upgrade || EXIT_CODE=$?
+
+ if [[ ${EXIT_CODE} -ne 0 ]]; then
+ echo -e "\033[31mPKG: ${PKG}; 'dart pub upgrade' - FAILED (${EXIT_CODE})\033[0m"
+ FAILURES+=("${PKG}; 'dart pub upgrade'")
+ else
+ for TASK in "$@"; do
+ EXIT_CODE=0
+ echo
+ echo -e "\033[1mPKG: ${PKG}; TASK: ${TASK}\033[22m"
+ case ${TASK} in
+ analyze_0)
+ echo 'dart analyze --fatal-infos'
+ dart analyze --fatal-infos || EXIT_CODE=$?
+ ;;
+ analyze_1)
+ echo 'dart analyze'
+ dart analyze || EXIT_CODE=$?
+ ;;
+ command_00)
+ echo 'dart test'
+ dart test || EXIT_CODE=$?
+ ;;
+ command_01)
+ echo 'xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 0'
+ xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 0 || EXIT_CODE=$?
+ ;;
+ command_02)
+ echo 'xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 1'
+ xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 1 || EXIT_CODE=$?
+ ;;
+ command_03)
+ echo 'xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 2'
+ xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 2 || EXIT_CODE=$?
+ ;;
+ command_04)
+ echo 'xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 3'
+ xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 3 || EXIT_CODE=$?
+ ;;
+ command_05)
+ echo 'xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 4'
+ xvfb-run -s "-screen 0 1024x768x24" dart test --preset travis --total-shards 5 --shard-index 4 || EXIT_CODE=$?
+ ;;
+ command_06)
+ echo 'dart test --preset travis --total-shards 5 --shard-index 0'
+ dart test --preset travis --total-shards 5 --shard-index 0 || EXIT_CODE=$?
+ ;;
+ command_07)
+ echo 'dart test --preset travis --total-shards 5 --shard-index 1'
+ dart test --preset travis --total-shards 5 --shard-index 1 || EXIT_CODE=$?
+ ;;
+ command_08)
+ echo 'dart test --preset travis --total-shards 5 --shard-index 2'
+ dart test --preset travis --total-shards 5 --shard-index 2 || EXIT_CODE=$?
+ ;;
+ command_09)
+ echo 'dart test --preset travis --total-shards 5 --shard-index 3'
+ dart test --preset travis --total-shards 5 --shard-index 3 || EXIT_CODE=$?
+ ;;
+ command_10)
+ echo 'dart test --preset travis --total-shards 5 --shard-index 4'
+ dart test --preset travis --total-shards 5 --shard-index 4 || EXIT_CODE=$?
+ ;;
+ command_11)
+ echo 'dart test --preset travis -x browser'
+ dart test --preset travis -x browser || EXIT_CODE=$?
+ ;;
+ format)
+ echo 'dart format --output=none --set-exit-if-changed .'
+ dart format --output=none --set-exit-if-changed . || EXIT_CODE=$?
+ ;;
+ test_1)
+ echo 'dart test -p chrome,vm,node'
+ dart test -p chrome,vm,node || EXIT_CODE=$?
+ ;;
+ test_2)
+ echo 'dart test --timeout=60s'
+ dart test --timeout=60s || EXIT_CODE=$?
+ ;;
+ *)
+ echo -e "\033[31mUnknown TASK '${TASK}' - TERMINATING JOB\033[0m"
+ exit 64
+ ;;
+ esac
+
+ if [[ ${EXIT_CODE} -ne 0 ]]; then
+ echo -e "\033[31mPKG: ${PKG}; TASK: ${TASK} - FAILED (${EXIT_CODE})\033[0m"
+ FAILURES+=("${PKG}; TASK: ${TASK}")
+ else
+ echo -e "\033[32mPKG: ${PKG}; TASK: ${TASK} - SUCCEEDED\033[0m"
+ SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
+ fi
+
+ done
+ fi
+
+ echo
+ echo -e "\033[32mSUCCESS COUNT: ${SUCCESS_COUNT}\033[0m"
+
+ if [ ${#FAILURES[@]} -ne 0 ]; then
+ echo -e "\033[31mFAILURES: ${#FAILURES[@]}\033[0m"
+ for i in "${FAILURES[@]}"; do
+ echo -e "\033[31m $i\033[0m"
+ done
+ fi
+
+ popd >/dev/null || exit 70
+ echo
+done
+
+if [ ${#FAILURES[@]} -ne 0 ]; then
+ exit 1
+fi