diff --git a/.github/ISSUE_TEMPLATE/fake_async.md b/.github/ISSUE_TEMPLATE/fake_async.md new file mode 100644 index 000000000..c26edd2ec --- /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/workflows/dart.yml b/.github/workflows/dart.yml index e1b92f73a..6f76daaf7 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -34,12 +34,42 @@ jobs: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - 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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 + 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@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: "3.3.0" + - id: checkout + name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - 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.5.0-311.0.dev; PKGS: integration_tests/regression, integration_tests/wasm; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos`" runs-on: ubuntu-latest steps: @@ -59,7 +89,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_regression_pub_upgrade name: integration_tests/regression; dart pub upgrade run: dart pub upgrade @@ -86,7 +116,7 @@ jobs: run: dart analyze --fatal-infos if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'" working-directory: integration_tests/wasm - job_003: + job_004: name: "analyze_and_format; linux; Dart 3.5.0-311.0.dev; PKGS: pkgs/checks, pkgs/test_core; `dart analyze`" runs-on: ubuntu-latest steps: @@ -106,7 +136,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_checks_pub_upgrade name: pkgs/checks; dart pub upgrade run: dart pub upgrade @@ -125,17 +155,17 @@ jobs: run: dart analyze if: "always() && steps.pkgs_test_core_pub_upgrade.conclusion == 'success'" working-directory: pkgs/test_core - job_004: - name: "analyze_and_format; linux; Dart dev; PKGS: integration_tests/regression, integration_tests/spawn_hybrid, integration_tests/wasm, pkgs/checks, pkgs/test, pkgs/test_api, pkgs/test_core; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos`" + job_005: + name: "analyze_and_format; linux; Dart dev; PKGS: integration_tests/regression, integration_tests/spawn_hybrid, integration_tests/wasm, pkgs/checks, pkgs/fake_async, 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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 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/test-pkgs/test_api-pkgs/test_core;commands:format-analyze_0" + 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/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/test-pkgs/test_api-pkgs/test_core + 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/test-pkgs/test_api-pkgs/test_core os:ubuntu-latest;pub-cache-hosted;sdk:dev os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest @@ -145,7 +175,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_regression_pub_upgrade name: integration_tests/regression; dart pub upgrade run: dart pub upgrade @@ -198,6 +228,19 @@ jobs: 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_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -237,7 +280,7 @@ jobs: run: dart analyze --fatal-infos if: "always() && steps.pkgs_test_core_pub_upgrade.conclusion == 'success'" working-directory: pkgs/test_core - job_005: + job_006: name: "analyze_and_format; windows; Dart 3.5.0-311.0.dev; PKG: integration_tests/wasm; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos`" runs-on: windows-latest steps: @@ -247,7 +290,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_wasm_pub_upgrade name: integration_tests/wasm; dart pub upgrade run: dart pub upgrade @@ -261,7 +304,7 @@ jobs: run: dart analyze --fatal-infos if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'" working-directory: integration_tests/wasm - job_006: + job_007: 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: @@ -271,7 +314,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_wasm_pub_upgrade name: integration_tests/wasm; dart pub upgrade run: dart pub upgrade @@ -285,7 +328,45 @@ jobs: run: dart analyze --fatal-infos if: "always() && steps.integration_tests_wasm_pub_upgrade.conclusion == 'success'" working-directory: integration_tests/wasm - job_007: + job_008: + 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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 + 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@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: "3.3.0" + - id: checkout + name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - 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_009: name: "unit_test; linux; Dart 3.5.0-311.0.dev; PKG: integration_tests/regression; `dart test`" runs-on: ubuntu-latest steps: @@ -305,7 +386,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_regression_pub_upgrade name: integration_tests/regression; dart pub upgrade run: dart pub upgrade @@ -322,7 +403,8 @@ jobs: - job_004 - job_005 - job_006 - job_008: + - job_007 + job_010: name: "unit_test; linux; Dart 3.5.0-311.0.dev; PKG: pkgs/checks; `dart test`" runs-on: ubuntu-latest steps: @@ -342,7 +424,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_checks_pub_upgrade name: pkgs/checks; dart pub upgrade run: dart pub upgrade @@ -359,7 +441,8 @@ jobs: - job_004 - job_005 - job_006 - job_009: + - job_007 + job_011: name: "unit_test; linux; Dart 3.5.0-311.0.dev; PKG: pkgs/test_core; `dart test`" runs-on: ubuntu-latest steps: @@ -379,7 +462,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_core_pub_upgrade name: pkgs/test_core; dart pub upgrade run: dart pub upgrade @@ -396,7 +479,8 @@ jobs: - job_004 - job_005 - job_006 - job_010: + - job_007 + job_012: name: "unit_test; linux; Dart 3.5.0-311.0.dev; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`" runs-on: ubuntu-latest steps: @@ -416,7 +500,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_spawn_hybrid_pub_upgrade name: integration_tests/spawn_hybrid; dart pub upgrade run: dart pub upgrade @@ -433,7 +517,8 @@ jobs: - job_004 - job_005 - job_006 - job_011: + - job_007 + job_013: name: "unit_test; linux; Dart 3.5.0-311.0.dev; PKG: integration_tests/wasm; `dart test --timeout=60s`" runs-on: ubuntu-latest steps: @@ -453,7 +538,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_wasm_pub_upgrade name: integration_tests/wasm; dart pub upgrade run: dart pub upgrade @@ -470,7 +555,8 @@ jobs: - job_004 - job_005 - job_006 - job_012: + - job_007 + job_014: name: "unit_test; linux; Dart 3.5.0-311.0.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: @@ -490,7 +576,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -507,7 +593,8 @@ jobs: - job_004 - job_005 - job_006 - job_013: + - job_007 + job_015: name: "unit_test; linux; Dart 3.5.0-311.0.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: @@ -527,7 +614,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -544,7 +631,8 @@ jobs: - job_004 - job_005 - job_006 - job_014: + - job_007 + job_016: name: "unit_test; linux; Dart 3.5.0-311.0.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: @@ -564,7 +652,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -581,7 +669,8 @@ jobs: - job_004 - job_005 - job_006 - job_015: + - job_007 + job_017: name: "unit_test; linux; Dart 3.5.0-311.0.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: @@ -601,7 +690,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -618,7 +707,8 @@ jobs: - job_004 - job_005 - job_006 - job_016: + - job_007 + job_018: name: "unit_test; linux; Dart 3.5.0-311.0.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: @@ -638,7 +728,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -655,7 +745,8 @@ jobs: - job_004 - job_005 - job_006 - job_017: + - job_007 + job_019: name: "unit_test; linux; Dart 3.5.0-311.0.dev; PKG: pkgs/test_api; `dart test --preset travis -x browser`" runs-on: ubuntu-latest steps: @@ -675,7 +766,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_api_pub_upgrade name: pkgs/test_api; dart pub upgrade run: dart pub upgrade @@ -692,7 +783,8 @@ jobs: - job_004 - job_005 - job_006 - job_018: + - job_007 + job_020: name: "unit_test; linux; Dart dev; PKG: integration_tests/regression; `dart test`" runs-on: ubuntu-latest steps: @@ -712,7 +804,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_regression_pub_upgrade name: integration_tests/regression; dart pub upgrade run: dart pub upgrade @@ -729,7 +821,8 @@ jobs: - job_004 - job_005 - job_006 - job_019: + - job_007 + job_021: name: "unit_test; linux; Dart dev; PKG: pkgs/checks; `dart test`" runs-on: ubuntu-latest steps: @@ -749,7 +842,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_checks_pub_upgrade name: pkgs/checks; dart pub upgrade run: dart pub upgrade @@ -766,7 +859,46 @@ jobs: - job_004 - job_005 - job_006 - job_020: + - job_007 + job_022: + 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@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 + 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@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: dev + - id: checkout + name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - 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_023: name: "unit_test; linux; Dart dev; PKG: pkgs/test_core; `dart test`" runs-on: ubuntu-latest steps: @@ -786,7 +918,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_core_pub_upgrade name: pkgs/test_core; dart pub upgrade run: dart pub upgrade @@ -803,7 +935,8 @@ jobs: - job_004 - job_005 - job_006 - job_021: + - job_007 + job_024: name: "unit_test; linux; Dart dev; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`" runs-on: ubuntu-latest steps: @@ -823,7 +956,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_spawn_hybrid_pub_upgrade name: integration_tests/spawn_hybrid; dart pub upgrade run: dart pub upgrade @@ -840,7 +973,8 @@ jobs: - job_004 - job_005 - job_006 - job_022: + - job_007 + job_025: name: "unit_test; linux; Dart dev; PKG: integration_tests/wasm; `dart test --timeout=60s`" runs-on: ubuntu-latest steps: @@ -860,7 +994,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_wasm_pub_upgrade name: integration_tests/wasm; dart pub upgrade run: dart pub upgrade @@ -877,7 +1011,8 @@ jobs: - job_004 - job_005 - job_006 - job_023: + - job_007 + job_026: 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: @@ -897,7 +1032,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -914,7 +1049,8 @@ jobs: - job_004 - job_005 - job_006 - job_024: + - job_007 + job_027: 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: @@ -934,7 +1070,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -951,7 +1087,8 @@ jobs: - job_004 - job_005 - job_006 - job_025: + - job_007 + job_028: 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: @@ -971,7 +1108,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -988,7 +1125,8 @@ jobs: - job_004 - job_005 - job_006 - job_026: + - job_007 + 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 3`" runs-on: ubuntu-latest steps: @@ -1008,7 +1146,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1025,7 +1163,8 @@ jobs: - job_004 - job_005 - job_006 - job_027: + - job_007 + 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 4`" runs-on: ubuntu-latest steps: @@ -1045,7 +1184,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1062,7 +1201,8 @@ jobs: - job_004 - job_005 - job_006 - job_028: + - job_007 + job_031: name: "unit_test; linux; Dart dev; PKG: pkgs/test_api; `dart test --preset travis -x browser`" runs-on: ubuntu-latest steps: @@ -1082,7 +1222,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_api_pub_upgrade name: pkgs/test_api; dart pub upgrade run: dart pub upgrade @@ -1099,7 +1239,8 @@ jobs: - job_004 - job_005 - job_006 - job_029: + - job_007 + job_032: name: "unit_test; osx; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 0`" runs-on: macos-latest steps: @@ -1119,7 +1260,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1136,7 +1277,8 @@ jobs: - job_004 - job_005 - job_006 - job_030: + - job_007 + job_033: name: "unit_test; osx; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 1`" runs-on: macos-latest steps: @@ -1156,7 +1298,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1173,7 +1315,8 @@ jobs: - job_004 - job_005 - job_006 - job_031: + - job_007 + job_034: name: "unit_test; osx; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 2`" runs-on: macos-latest steps: @@ -1193,7 +1336,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1210,7 +1353,8 @@ jobs: - job_004 - job_005 - job_006 - job_032: + - job_007 + job_035: name: "unit_test; osx; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 3`" runs-on: macos-latest steps: @@ -1230,7 +1374,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1247,7 +1391,8 @@ jobs: - job_004 - job_005 - job_006 - job_033: + - job_007 + job_036: name: "unit_test; osx; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 4`" runs-on: macos-latest steps: @@ -1267,7 +1412,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1284,7 +1429,8 @@ jobs: - job_004 - job_005 - job_006 - job_034: + - job_007 + job_037: name: "unit_test; windows; Dart 3.5.0-311.0.dev; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`" runs-on: windows-latest steps: @@ -1294,7 +1440,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_spawn_hybrid_pub_upgrade name: integration_tests/spawn_hybrid; dart pub upgrade run: dart pub upgrade @@ -1311,7 +1457,8 @@ jobs: - job_004 - job_005 - job_006 - job_035: + - job_007 + job_038: name: "unit_test; windows; Dart 3.5.0-311.0.dev; PKG: integration_tests/wasm; `dart test --timeout=60s`" runs-on: windows-latest steps: @@ -1321,7 +1468,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_wasm_pub_upgrade name: integration_tests/wasm; dart pub upgrade run: dart pub upgrade @@ -1338,7 +1485,8 @@ jobs: - job_004 - job_005 - job_006 - job_036: + - job_007 + job_039: name: "unit_test; windows; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 0`" runs-on: windows-latest steps: @@ -1348,7 +1496,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1365,7 +1513,8 @@ jobs: - job_004 - job_005 - job_006 - job_037: + - job_007 + job_040: name: "unit_test; windows; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 1`" runs-on: windows-latest steps: @@ -1375,7 +1524,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1392,7 +1541,8 @@ jobs: - job_004 - job_005 - job_006 - job_038: + - job_007 + job_041: name: "unit_test; windows; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 2`" runs-on: windows-latest steps: @@ -1402,7 +1552,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1419,7 +1569,8 @@ jobs: - job_004 - job_005 - job_006 - job_039: + - job_007 + job_042: name: "unit_test; windows; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 3`" runs-on: windows-latest steps: @@ -1429,7 +1580,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1446,7 +1597,8 @@ jobs: - job_004 - job_005 - job_006 - job_040: + - job_007 + job_043: name: "unit_test; windows; Dart 3.5.0-311.0.dev; PKG: pkgs/test; `dart test --preset travis --total-shards 5 --shard-index 4`" runs-on: windows-latest steps: @@ -1456,7 +1608,7 @@ jobs: sdk: "3.5.0-311.0.dev" - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: pkgs_test_pub_upgrade name: pkgs/test; dart pub upgrade run: dart pub upgrade @@ -1473,7 +1625,8 @@ jobs: - job_004 - job_005 - job_006 - job_041: + - job_007 + job_044: name: "unit_test; windows; Dart dev; PKG: integration_tests/spawn_hybrid; `dart test -p chrome,vm,node`" runs-on: windows-latest steps: @@ -1483,7 +1636,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_spawn_hybrid_pub_upgrade name: integration_tests/spawn_hybrid; dart pub upgrade run: dart pub upgrade @@ -1500,7 +1653,8 @@ jobs: - job_004 - job_005 - job_006 - job_042: + - job_007 + job_045: name: "unit_test; windows; Dart dev; PKG: integration_tests/wasm; `dart test --timeout=60s`" runs-on: windows-latest steps: @@ -1510,7 +1664,7 @@ jobs: sdk: dev - id: checkout name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - id: integration_tests_wasm_pub_upgrade name: integration_tests/wasm; dart pub upgrade run: dart pub upgrade @@ -1527,7 +1681,8 @@ jobs: - job_004 - job_005 - job_006 - job_043: + - job_007 + job_046: name: Notify failure runs-on: ubuntu-latest if: "(github.event_name == 'push' || github.event_name == 'schedule') && failure()" @@ -1581,3 +1736,6 @@ jobs: - job_040 - job_041 - job_042 + - job_043 + - job_044 + - job_045 diff --git a/README.md b/README.md index 7091bd97c..df1413b87 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ literate API. | Package | Description | Version | |---|---|---| | [checks](pkgs/checks/) | A framework for checking values against expectations and building custom expectations. | [![pub package](https://img.shields.io/pub/v/checks.svg)](https://pub.dev/packages/checks) | +| [fake_async](pkgs/fake_async/) | Fake asynchronous events such as timers and microtasks for deterministic testing. | [![pub package](https://img.shields.io/pub/v/fake_async.svg)](https://pub.dev/packages/fake_async) | | [test](pkgs/test/) | A full featured library for writing and running Dart tests across platforms. | [![pub package](https://img.shields.io/pub/v/test.svg)](https://pub.dev/packages/test) | | [test_api](pkgs/test_api/) | | [![pub package](https://img.shields.io/pub/v/test_api.svg)](https://pub.dev/packages/test_api) | | [test_core](pkgs/test_core/) | | [![pub package](https://img.shields.io/pub/v/test_core.svg)](https://pub.dev/packages/test_core) | diff --git a/pkgs/fake_async/.gitignore b/pkgs/fake_async/.gitignore new file mode 100644 index 000000000..63fe85d83 --- /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 000000000..e8063a8cd --- /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 + +Google Inc. diff --git a/pkgs/fake_async/CHANGELOG.md b/pkgs/fake_async/CHANGELOG.md new file mode 100644 index 000000000..0f964eede --- /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 000000000..7a4a3ea24 --- /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 000000000..c7bb40bf4 --- /dev/null +++ b/pkgs/fake_async/README.md @@ -0,0 +1,51 @@ +[![pub package](https://img.shields.io/pub/v/fake_async.svg)](https://pub.dev/packages/fake_async) +[![package publisher](https://img.shields.io/pub/publisher/fake_async.svg)](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())); + + // 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 000000000..c0b72ff33 --- /dev/null +++ b/pkgs/fake_async/analysis_options.yaml @@ -0,0 +1,22 @@ +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 + - package_api_docs + - 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 000000000..b4eea856b --- /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 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 = {}; + + /// All the current pending timers. + List get pendingTimers => _timers.toList(growable: false); + + /// The debug strings for all the current pending timers. + List 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 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 000000000..b447edbf8 --- /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 000000000..f345860ba --- /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 000000000..463eecdb8 --- /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 = []; + 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 = []; + 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 = []; + 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(); + 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(); + expect(completer.future.timeout(elapseBy ~/ 2), + throwsA(const TypeMatcher())); + 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(); + final timed = controller.stream.timeout(const Duration(minutes: 2)); + + final events = []; + final errors = []; + 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()); + }); + }); + }); + }); + + 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 = []; + 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 = []; + 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 = []; + 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 = []; + 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 = []; + 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 = []; + 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 = []; + 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');