diff --git a/.ci.yaml b/.ci.yaml index f9a936f0e898a..0e7856c0d5c5e 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -292,31 +292,6 @@ targets: release_build: "true" config_name: linux_web_engine - - name: Linux Web Engine - recipe: engine/web_engine - properties: - add_recipes_cq: "true" - cores: "32" - gcs_goldens_bucket: flutter_logs - gclient_variables: >- - {"download_emsdk": true} - dependencies: >- - [ - {"dependency": "chrome_and_driver", "version": "version:111.0"}, - {"dependency": "firefox", "version": "version:106.0"}, - {"dependency": "goldctl", "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603"} - ] - no_goma: "true" - timeout: 60 - runIf: - - DEPS - - .ci.yaml - - lib/web_ui/** - - web_sdk/** - - tools/** - - ci/** - - flutter_frontend_server/** - - name: Linux Web Framework tests recipe: engine/web_engine_framework enabled_branches: @@ -448,28 +423,6 @@ targets: ios_debug: "true" timeout: 60 - - name: Mac Web Engine - recipe: engine/web_engine - properties: - add_recipes_cq: "true" - gcs_goldens_bucket: flutter_logs - gclient_variables: >- - {"download_emsdk": true} - dependencies: >- - [ - {"dependency": "goldctl", "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603"} - ] - no_goma: "true" - timeout: 60 - runIf: - - DEPS - - .ci.yaml - - lib/web_ui/** - - web_sdk/** - - tools/** - - ci/** - - flutter_frontend_server/** - - name: Mac mac_ios_engine recipe: engine_v2/engine_v2 timeout: 60 @@ -530,24 +483,6 @@ targets: add_recipes_cq: "true" timeout: 75 - - name: Windows Web Engine - recipe: engine/web_engine - properties: - gclient_variables: >- - {"download_emsdk": true} - gcs_goldens_bucket: flutter_logs - dependencies: >- - [ - {"dependency": "chrome_and_driver", "version": "version:111.0"} - ] - no_goma: "true" - timeout: 60 - runIf: - - DEPS - - .ci.yaml - - lib/web_ui/** - - web_sdk/** - - name: Mac iOS Engine Profile recipe: engine/engine properties: diff --git a/ci/builders/linux_web_engine.json b/ci/builders/linux_web_engine.json index 6cb52d450e18a..351a6777614e3 100644 --- a/ci/builders/linux_web_engine.json +++ b/ci/builders/linux_web_engine.json @@ -1,127 +1,1840 @@ { - "builds": [ - { - "archives": [ - { - "name": "wasm_release", - "base_path": "out/wasm_release/zip_archives/", - "type": "gcs", - "include_paths": [ - "out/wasm_release/zip_archives/flutter-web-sdk.zip" - ], - "realm": "production" - } + "builds": [ + { + "name": "web_tests/artifacts", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "gclient_variables": { + "download_android_deps": false, + "download_emsdk": true + }, + "gn": [ + "--web", + "--runtime-mode=release", + "--no-goma" + ], + "ninja": { + "config": "wasm_release", + "targets": [ + "flutter/web_sdk:flutter_web_sdk_archive" + ] + }, + "archives": [ + { + "name": "wasm_release", + "base_path": "out/wasm_release/zip_archives/", + "type": "gcs", + "include_paths": [ + "out/wasm_release/zip_archives/flutter-web-sdk.zip" + ], + "realm": "production" + } + ], + "generators": { + "tasks": [ + { + "name": "check licenses", + "parameters": [ + "check-licenses" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + }, + { + "name": "web engine analysis", + "parameters": [ + "analyze" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + }, + { + "name": "copy artifacts for web tests", + "parameters": [ + "test", + "--copy-artifacts" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2js-html-engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2js-html-engine", + "parameters": [ + "test", + "--compile", + "--bundle=dart2js-html-engine" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2js-html-html", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2js-html-html", + "parameters": [ + "test", + "--compile", + "--bundle=dart2js-html-html" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2js-html-ui", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2js-html-ui", + "parameters": [ + "test", + "--compile", + "--bundle=dart2js-html-ui" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2js-canvaskit-canvaskit", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--compile", + "--bundle=dart2js-canvaskit-canvaskit" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2js-canvaskit-ui", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2js-canvaskit-ui", + "parameters": [ + "test", + "--compile", + "--bundle=dart2js-canvaskit-ui" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2js-skwasm-skwasm_stub", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2js-skwasm-skwasm_stub", + "parameters": [ + "test", + "--compile", + "--bundle=dart2js-skwasm-skwasm_stub" ], - "drone_dimensions": [ - "device_type=none", - "os=Linux", - "cores=32" + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2wasm-html-engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2wasm-html-engine", + "parameters": [ + "test", + "--compile", + "--bundle=dart2wasm-html-engine" ], - "gclient_variables": { - "download_android_deps": false, - "download_emsdk": true - }, - "gn": [ - "--build-canvaskit", - "--web", - "--runtime-mode=release", - "--no-goma" + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2wasm-html-html", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2wasm-html-html", + "parameters": [ + "test", + "--compile", + "--bundle=dart2wasm-html-html" ], - "name": "wasm_release", - "ninja": { - "config": "wasm_release", - "targets": [ - "flutter/web_sdk:flutter_web_sdk_archive" - ] - }, - "generators": { - "pub_dirs": [ - "flutter/lib/web_ui/", - "flutter/web_sdk/web_engine_tester/" - ], - "tasks": [ - { - "name": "check licenses", - "parameters": [ - "check-licenses" - ], - "scripts": [ - "flutter/lib/web_ui/dev/felt" - ] - }, - { - "name": "web engine analysis", - "parameters": [ - "analyze" - ], - "scripts": [ - "flutter/lib/web_ui/dev/felt" - ] - } - ] - }, - "tests": [] - }, - { - "archives": [], - "drone_dimensions": [ - "device_type=none", - "os=Linux" + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2wasm-html-ui", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2wasm-html-ui", + "parameters": [ + "test", + "--compile", + "--bundle=dart2wasm-html-ui" ], - "gclient_variables": { - "download_android_deps": false - }, - "name": "web_tests", - "generators": { - "tasks": [ - { - "name": "compile web_tests", - "parameters": [ - "run", - "compile_tests" - ], - "scripts": [ - "flutter/lib/web_ui/dev/felt" - ] - } - ] - } - } - - ], - "tests": [ - { - "name": "chrome-unit-linux", - "drone_dimensions": [ - "device_type=none", - "os=Linux" + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2wasm-canvaskit-canvaskit", + "parameters": [ + "test", + "--compile", + "--bundle=dart2wasm-canvaskit-canvaskit" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2wasm-canvaskit-ui", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2wasm-canvaskit-ui", + "parameters": [ + "test", + "--compile", + "--bundle=dart2wasm-canvaskit-ui" + ], + "scripts": [ + "flutter/lib/web_ui/dev/felt" + ] + } + ] + } + }, + { + "name": "web_tests/test_bundles/dart2wasm-skwasm-ui", + "drone_dimensions": [ + "device_type=none", + "os=Linux", + "cores=32" + ], + "generators": { + "tasks": [ + { + "name": "compile bundle dart2wasm-skwasm-ui", + "parameters": [ + "test", + "--compile", + "--bundle=dart2wasm-skwasm-ui" ], - "dependencies": ["wasm_release", "web_tests"], - "test_dependencies": [ - { - "dependency": "chrome_and_driver", - "version": "version:111.0" - }, - { - "dependency": "goldctl", - "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" - } - ], - "recipe": "engine_v2/tester_engine", - "gclient_variables": { - "download_android_deps": false - }, - "tasks": [ - { - "name": "felt test: chrome-unit-linux", - "parameters": [ - "test", - "--browser=chrome", - "--require-skia-gold" - ], - "script": "flutter/lib/web_ui/dev/felt" - } + "scripts": [ + "flutter/lib/web_ui/dev/felt" ] - } - ] + } + ] + } + } + ], + "tests": [ + { + "name": "Linux run chrome-dart2js-html-engine suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-engine" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-html-engine", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-html-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2js-html-html suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-html" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-html-html", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-html-html" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2js-html-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-html-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-html-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2js-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2js-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2js-skwasm-skwasm_stub suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-skwasm-skwasm_stub" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-skwasm-skwasm_stub", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-skwasm-skwasm_stub" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-full-dart2js-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-full-dart2js-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run firefox-dart2js-html-engine suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-engine" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "firefox", + "version": "version:106.0" + } + ], + "tasks": [ + { + "name": "run suite firefox-dart2js-html-engine", + "parameters": [ + "test", + "--run", + "--suite=firefox-dart2js-html-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run firefox-dart2js-html-html suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-html" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "firefox", + "version": "version:106.0" + } + ], + "tasks": [ + { + "name": "run suite firefox-dart2js-html-html", + "parameters": [ + "test", + "--run", + "--suite=firefox-dart2js-html-html" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run firefox-dart2js-html-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "firefox", + "version": "version:106.0" + } + ], + "tasks": [ + { + "name": "run suite firefox-dart2js-html-ui", + "parameters": [ + "test", + "--run", + "--suite=firefox-dart2js-html-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run firefox-dart2js-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "firefox", + "version": "version:106.0" + } + ], + "tasks": [ + { + "name": "run suite firefox-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=firefox-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run firefox-dart2js-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "firefox", + "version": "version:106.0" + } + ], + "tasks": [ + { + "name": "run suite firefox-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=firefox-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2wasm-html-engine suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-html-engine" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-html-engine", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-html-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2wasm-html-html suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-html-html" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-html-html", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-html-html" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2wasm-html-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-html-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-html-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-html-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2wasm-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2wasm-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-dart2wasm-skwasm-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-skwasm-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-skwasm-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-skwasm-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-full-dart2wasm-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2wasm-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2wasm-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Linux run chrome-full-dart2wasm-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Linux" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2wasm-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2wasm-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Mac run safari-dart2js-html-engine suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Mac" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-engine" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + } + ], + "tasks": [ + { + "name": "run suite safari-dart2js-html-engine", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-html-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Mac run safari-dart2js-html-html suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Mac" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-html" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + } + ], + "tasks": [ + { + "name": "run suite safari-dart2js-html-html", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-html-html" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Mac run safari-dart2js-html-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Mac" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + } + ], + "tasks": [ + { + "name": "run suite safari-dart2js-html-ui", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-html-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Mac run safari-dart2js-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Mac" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + } + ], + "tasks": [ + { + "name": "run suite safari-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Mac run safari-dart2js-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Mac" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + } + ], + "tasks": [ + { + "name": "run suite safari-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=safari-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2js-html-engine suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-engine" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-html-engine", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-html-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2js-html-html suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-html" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-html-html", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-html-html" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2js-html-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-html-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-html-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-html-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2js-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2js-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2js-skwasm-skwasm_stub suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-skwasm-skwasm_stub" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2js-skwasm-skwasm_stub", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2js-skwasm-skwasm_stub" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-full-dart2js-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2js-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-full-dart2js-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2js-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2js-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2js-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2wasm-html-engine suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-html-engine" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-html-engine", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-html-engine" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2wasm-html-html suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-html-html" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-html-html", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-html-html" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2wasm-html-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-html-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-html-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-html-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2wasm-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2wasm-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-dart2wasm-skwasm-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-skwasm-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-dart2wasm-skwasm-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-dart2wasm-skwasm-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-full-dart2wasm-canvaskit-canvaskit suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-canvaskit" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2wasm-canvaskit-canvaskit", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2wasm-canvaskit-canvaskit" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + }, + { + "name": "Windows run chrome-full-dart2wasm-canvaskit-ui suite", + "recipe": "engine_v2/tester_engine", + "drone_dimensions": [ + "device_type=none", + "os=Windows" + ], + "gclient_variables": { + "download_android_deps": false + }, + "dependencies": [ + "web_tests/artifacts", + "web_tests/test_bundles/dart2wasm-canvaskit-ui" + ], + "test_dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603" + }, + { + "dependency": "chrome_and_driver", + "version": "version:111.0" + } + ], + "tasks": [ + { + "name": "run suite chrome-full-dart2wasm-canvaskit-ui", + "parameters": [ + "test", + "--run", + "--suite=chrome-full-dart2wasm-canvaskit-ui" + ], + "script": "flutter/lib/web_ui/dev/felt" + } + ] + } + ] } diff --git a/lib/web_ui/README.md b/lib/web_ui/README.md index 5c19ffdaade14..8c050c389b428 100644 --- a/lib/web_ui/README.md +++ b/lib/web_ui/README.md @@ -22,75 +22,92 @@ To tell `felt` to do anything you call `felt SUBCOMMAND`, where `SUBCOMMAND` is one of the available subcommands, which can be listed by running `felt help`. To get help for a specific subcommand, run `felt help SUBCOMMAND`. -The most useful subcommands are: - -- `felt build` - builds a local Flutter Web engine ready to be used by the - Flutter framework. To use a locally built web sdk, build with `felt build`, - then pass `--local-web-sdk=wasm_release` to the `flutter` command, or to - `dev/bots/test.dart` when running a web shard, such as `web_tests`. -- `felt test` - runs web engine tests. By default, this runs all tests using - Chromium. Passing one or more paths to specific tests would run just the - specified tests. Run `felt help test` for more options. - -`build` and `test` take the `--watch` option, which automatically reruns the -subcommand when a source file changes. This is handy when you are iterating -quickly. - -#### Examples - -Builds the web engine, the runs a Flutter app using it: - +#### `felt build` +The `build` subcommand builds web engine gn/ninja targets. Targets can be +individually specified in the command line invocation, or if none are specified, +all web engine targets are built. Common targets are as follows: + * `sdk` - The flutter_web_sdk itself. + * `canvaskit` - Flutter's version of canvakit. + * `canvaskit_chromium` - A version of canvaskit optimized for use with + chromium-based browsers. + * `skwasm` - Builds experimental skia wasm module renderer. +The output of these steps is used in unit tests, and can be used with the flutter +command via the `--local-web-sdk=wasm_release` command. + +##### Examples +Builds all web engine targets, then runs a Flutter app using it: ``` felt build cd path/to/some/app flutter --local-web-sdk=wasm_release run -d chrome ``` -Runs all tests in Chromium: - +Builds only the `sdk` and the `canvaskit` targets: ``` -felt test +felt build sdk canvaskit ``` -Runs a specific test: - +#### `felt test` +The `test` subcommand will compile and/or run web engine unit test suites. For +information on how test suites are structured, see the test configuration +[readme][2]. + +By default, `felt test` compiles and runs all suites that are compatible with the +host system. Some useful flags supported by this command: + * `--compile` will only perform compilation of these suites without running them. + * `--run` will only run the tests and not compile them, and assume they have been + compiled in a previous run of the tool. + * `--list` will list all the test suites and test bundles and exit without + compiling or running anything. + * `--verbose` will output some extra information that may be useful for debugging. + * `--debug` will open a browser window and pause the tests before starting so that + breakpoints can be set before starting the test suites. + +Several other flags can be passed that filter which test suites should be run: + * `--browser` runs only the test suites that test on the browsers passed. Valid + values for this are `chrome`, `firefox`, `safari`, or `edge`. + * `--compiler` runs only the test suites that use a particular compiler. Valid + values for this are `dart2js` or `dart2wasm` + * `--renderer` runs only the test suites that use a particular renderer. Valid + values for this are `html`, `canvakit`, or `skwasm` + * `--suite` runs a suite by name. + * `--bundle` runs suites that target a particular test bundle. + +Filters of different types are logically ANDed together, but multiple filter flags +of the same type are logically ORed together. + +The `test` command will also accept a list of paths to specific test files to be +compiled and run. If none of these paths are specified, all tests are run, otherwise +only the tests that are specified will run. + +##### Examples +Runs all test suites in all compatible browsers: ``` -felt test test/engine/util_test.dart +felt test ``` - -Runs multiple specific tests: - +Runs a specific test on all compatible browsers: ``` -felt test test/engine/util_test.dart test/alarm_clock_test.dart +felt test test/engine/util_test.dart ``` - -Enable watch mode so that the test re-runs every time a source file changes: - +Runs multiple specific tests on all compatible browsers: ``` -felt test --watch test/engine/util_test.dart +felt test test/engine/util_test.dart test/engine/alarm_clock_test.dart ``` - -Runs tests in Firefox (requires a Linux computer): - +Runs only test suites that compile via dart2wasm: ``` -felt test --browser=firefox +felt test --compiler dart2wasm ``` - -Chromium and Firefox support debugging tests using the browser's developer -tools. To run tests in debug mode add `--debug` to the `test` command, e.g.: - +Runs only test suites that run in Chrome and Safari: ``` -felt test --debug --browser=firefox test/alarm_clock_test.dart +felt test --browser chrome --browser safari ``` ### Optimizing local builds Concurrency of various build steps can be configured via environment variables: -- `FELT_DART2JS_CONCURRENCY` specifies the number of concurrent `dart2js` +- `FELT_COMPILE_CONCURRENCY` specifies the number of concurrent compiler processes used to compile tests. Default value is 8. -- `FELT_TEST_CONCURRENCY` specifies the number of tests run concurrently. - Default value is 10. If you are a Google employee, you can use an internal instance of Goma (go/ma) to parallelize your ninja builds. Because Goma compiles code on remote servers, @@ -99,7 +116,7 @@ this option is particularly effective for building on low-powered laptops. ### Test browsers Chromium, Firefox, and Safari for iOS are version-locked using the -[browser_lock.yaml][2] configuration file. Safari for macOS is supplied by the +[browser_lock.yaml][3] configuration file. Safari for macOS is supplied by the computer's operating system. Tests can be run in Edge locally, but Edge is not enabled on LUCI. Chromium is used as a proxy for Chrome, Edge, and other Chromium-based browsers. @@ -273,7 +290,8 @@ Once you know the version for the Emscripten SDK, change the line in [1]: https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment -[2]: https://github.com/flutter/engine/blob/main/lib/web_ui/dev/browser_lock.yaml +[2]: https://github.com/flutter/flutter/blob/main/lib/web_ui/test/README +[3]: https://github.com/flutter/engine/blob/main/lib/web_ui/dev/browser_lock.yaml [4]: https://chrome-infra-packages.appspot.com/p/flutter_internal [5]: https://cs.opensource.google/flutter/recipes/+/master:recipes/engine/web_engine.py [6]: https://chromium.googlesource.com/chromium/src.git/+/main/docs/cipd_and_3pp.md#What-is-CIPD diff --git a/lib/web_ui/dev/common.dart b/lib/web_ui/dev/common.dart index eab1360ac87c2..cd61c8f39e196 100644 --- a/lib/web_ui/dev/common.dart +++ b/lib/web_ui/dev/common.dart @@ -11,6 +11,7 @@ import 'browser_lock.dart'; import 'chrome.dart'; import 'edge.dart'; import 'environment.dart'; +import 'felt_config.dart'; import 'firefox.dart'; import 'safari_macos.dart'; @@ -261,16 +262,15 @@ const List kAllBrowserNames = [ /// Creates an environment for a browser. /// /// The [browserName] matches the browser name passed as the `--browser` option. -BrowserEnvironment getBrowserEnvironment(String browserName, { required bool enableWasmGC }) { +BrowserEnvironment getBrowserEnvironment(BrowserName browserName, { required bool enableWasmGC }) { switch (browserName) { - case kChrome: + case BrowserName.chrome: return ChromeEnvironment(enableWasmGC); - case kEdge: + case BrowserName.edge: return EdgeEnvironment(); - case kFirefox: + case BrowserName.firefox: return FirefoxEnvironment(); - case kSafari: + case BrowserName.safari: return SafariMacOsEnvironment(); } - throw UnsupportedError('Browser $browserName is not supported.'); } diff --git a/lib/web_ui/dev/environment.dart b/lib/web_ui/dev/environment.dart index f1f628986423e..d9aac010b64af 100644 --- a/lib/web_ui/dev/environment.dart +++ b/lib/web_ui/dev/environment.dart @@ -148,12 +148,17 @@ class Environment { /// Path to the "build" directory, generated by "package:build_runner". /// - /// This is where compiled output goes. + /// This is where compiled test output goes. io.Directory get webUiBuildDir => io.Directory(pathlib.join( outDir.path, 'web_tests', )); + io.Directory get webTestsArtifactsDir => io.Directory(pathlib.join( + webUiBuildDir.path, + 'artifacts', + )); + /// Path to the ".dart_tool" directory, generated by various Dart tools. io.Directory get webUiDartToolDir => io.Directory(pathlib.join( webUiRootDir.path, diff --git a/lib/web_ui/dev/felt.dart b/lib/web_ui/dev/felt.dart index 1dc0ccbb74d00..3ed0837fe2af0 100644 --- a/lib/web_ui/dev/felt.dart +++ b/lib/web_ui/dev/felt.dart @@ -12,7 +12,6 @@ import 'clean.dart'; import 'exceptions.dart'; import 'generate_fallback_font_data.dart'; import 'licenses.dart'; -import 'run.dart'; import 'test_runner.dart'; import 'utils.dart'; @@ -25,7 +24,6 @@ CommandRunner runner = CommandRunner( ..addCommand(CleanCommand()) ..addCommand(GenerateFallbackFontDataCommand()) ..addCommand(LicensesCommand()) - ..addCommand(RunCommand()) ..addCommand(TestCommand()); Future main(List rawArgs) async { diff --git a/lib/web_ui/dev/felt_config.dart b/lib/web_ui/dev/felt_config.dart new file mode 100644 index 0000000000000..e3725a4ce1d01 --- /dev/null +++ b/lib/web_ui/dev/felt_config.dart @@ -0,0 +1,248 @@ +// Copyright 2013 The Flutter Authors. 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' as io; +import 'package:yaml/yaml.dart'; + +enum Compiler { + dart2js, + dart2wasm +} + +enum Renderer { + html, + canvaskit, + skwasm, +} + +class CompileConfiguration { + CompileConfiguration(this.name, this.compiler, this.renderer); + + final String name; + final Compiler compiler; + final Renderer renderer; +} + +class TestSet { + TestSet(this.name, this.directory); + + final String name; + final String directory; +} + +class TestBundle { + TestBundle(this.name, this.testSet, this.compileConfig); + + final String name; + final TestSet testSet; + final CompileConfiguration compileConfig; +} + +enum CanvasKitVariant { + full, + chromium, +} + +enum BrowserName { + chrome, + edge, + firefox, + safari, +} + +class RunConfiguration { + RunConfiguration(this.name, this.browser, this.variant); + + final String name; + final BrowserName browser; + final CanvasKitVariant? variant; +} + +class ArtifactDependencies { + ArtifactDependencies({ + required this.canvasKit, + required this.canvasKitChromium, + required this.skwasm + }); + + ArtifactDependencies.none() : + canvasKit = false, + canvasKitChromium = false, + skwasm = false; + final bool canvasKit; + final bool canvasKitChromium; + final bool skwasm; + + ArtifactDependencies operator|(ArtifactDependencies other) { + return ArtifactDependencies( + canvasKit: canvasKit || other.canvasKit, + canvasKitChromium: canvasKitChromium || other.canvasKitChromium, + skwasm: skwasm || other.skwasm, + ); + } + + ArtifactDependencies operator&(ArtifactDependencies other) { + return ArtifactDependencies( + canvasKit: canvasKit && other.canvasKit, + canvasKitChromium: canvasKitChromium && other.canvasKitChromium, + skwasm: skwasm && other.skwasm, + ); + } +} + +class TestSuite { + TestSuite( + this.name, + this.testBundle, + this.runConfig, + this.artifactDependencies + ); + + String name; + TestBundle testBundle; + RunConfiguration runConfig; + ArtifactDependencies artifactDependencies; +} + +class FeltConfig { + FeltConfig( + this.compileConfigs, + this.testSets, + this.testBundles, + this.runConfigs, + this.testSuites, + ); + + factory FeltConfig.fromFile(String filePath) { + final io.File configFile = io.File(filePath); + final YamlMap yaml = loadYaml(configFile.readAsStringSync()) as YamlMap; + + final List compileConfigs = []; + final Map compileConfigsByName = {}; + for (final dynamic node in yaml['compile-configs'] as YamlList) { + final YamlMap configYaml = node as YamlMap; + final String name = configYaml['name'] as String; + final Compiler compiler = Compiler.values.byName(configYaml['compiler'] as String); + final Renderer renderer = Renderer.values.byName(configYaml['renderer'] as String); + final CompileConfiguration config = CompileConfiguration(name, compiler, renderer); + compileConfigs.add(config); + if (compileConfigsByName.containsKey(name)) { + throw AssertionError('Duplicate compile config name: $name'); + } + compileConfigsByName[name] = config; + } + + final List testSets = []; + final Map testSetsByName = {}; + for (final dynamic node in yaml['test-sets'] as YamlList) { + final YamlMap testSetYaml = node as YamlMap; + final String name = testSetYaml['name'] as String; + final String directory = testSetYaml['directory'] as String; + final TestSet testSet = TestSet(name, directory); + testSets.add(testSet); + if (testSetsByName.containsKey(name)) { + throw AssertionError('Duplicate test set name: $name'); + } + testSetsByName[name] = testSet; + } + + final List testBundles = []; + final Map testBundlesByName = {}; + for (final dynamic node in yaml['test-bundles'] as YamlList) { + final YamlMap testBundleYaml = node as YamlMap; + final String name = testBundleYaml['name'] as String; + final String testSetName = testBundleYaml['test-set'] as String; + final TestSet? testSet = testSetsByName[testSetName]; + if (testSet == null) { + throw AssertionError('Test set not found with name: `$testSetName` (referenced by test bundle: `$name`)'); + } + final String compileConfigName = testBundleYaml['compile-config'] as String; + final CompileConfiguration? compileConfig = compileConfigsByName[compileConfigName]; + if (compileConfig == null) { + throw AssertionError('Compile config not found with name: `$compileConfigName` (referenced by test bundle: `$name`)'); + } + final TestBundle bundle = TestBundle(name, testSet, compileConfig); + testBundles.add(bundle); + if (testBundlesByName.containsKey(name)) { + throw AssertionError('Duplicate test bundle name: $name'); + } + testBundlesByName[name] = bundle; + } + + final List runConfigs = []; + final Map runConfigsByName = {}; + for (final dynamic node in yaml['run-configs'] as YamlList) { + final YamlMap runConfigYaml = node as YamlMap; + final String name = runConfigYaml['name'] as String; + final BrowserName browser = BrowserName.values.byName(runConfigYaml['browser'] as String); + final dynamic variantNode = runConfigYaml['canvaskit-variant']; + final CanvasKitVariant? variant = variantNode == null + ? null + : CanvasKitVariant.values.byName(variantNode as String); + final RunConfiguration runConfig = RunConfiguration(name, browser, variant); + runConfigs.add(runConfig); + if (runConfigsByName.containsKey(name)) { + throw AssertionError('Duplicate run config name: $name'); + } + runConfigsByName[name] = runConfig; + } + + final List testSuites = []; + for (final dynamic node in yaml['test-suites'] as YamlList) { + final YamlMap testSuiteYaml = node as YamlMap; + final String name = testSuiteYaml['name'] as String; + final String testBundleName = testSuiteYaml['test-bundle'] as String; + final TestBundle? bundle = testBundlesByName[testBundleName]; + if (bundle == null) { + throw AssertionError('Test bundle not found with name: `$testBundleName` (referenced by test suite: `$name`)'); + } + final String runConfigName = testSuiteYaml['run-config'] as String; + final RunConfiguration? runConfig = runConfigsByName[runConfigName]; + if (runConfig == null) { + throw AssertionError('Run config not found with name: `$runConfigName` (referenced by test suite: `$name`)'); + } + bool canvasKit = false; + bool canvasKitChromium = false; + bool skwasm = false; + final dynamic depsNode = testSuiteYaml['artifact-deps']; + if (depsNode != null) { + for (final dynamic dep in depsNode as YamlList) { + switch (dep as String) { + case 'canvaskit': + if (canvasKit) { + throw AssertionError('Artifact dep $dep listed twice in suite $name.'); + } + canvasKit = true; + case 'canvaskit_chromium': + if (canvasKitChromium) { + throw AssertionError('Artifact dep $dep listed twice in suite $name.'); + } + canvasKitChromium = true; + case 'skwasm': + if (skwasm) { + throw AssertionError('Artifact dep $dep listed twice in suite $name.'); + } + skwasm = true; + default: + throw AssertionError('Unrecognized artifact dependency: $dep'); + } + } + } + final ArtifactDependencies artifactDeps = ArtifactDependencies( + canvasKit: canvasKit, + canvasKitChromium: canvasKitChromium, + skwasm: skwasm + ); + final TestSuite suite = TestSuite(name, bundle, runConfig, artifactDeps); + testSuites.add(suite); + } + return FeltConfig(compileConfigs, testSets, testBundles, runConfigs, testSuites); + } + + List compileConfigs; + List testSets; + List testBundles; + List runConfigs; + List testSuites; +} diff --git a/lib/web_ui/dev/generate_builder_json.dart b/lib/web_ui/dev/generate_builder_json.dart new file mode 100644 index 0000000000000..719d088824f0f --- /dev/null +++ b/lib/web_ui/dev/generate_builder_json.dart @@ -0,0 +1,173 @@ +// Copyright 2013 The Flutter Authors. 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 'felt_config.dart'; + +String generateBuilderJson(FeltConfig config) { + final Map outputJson = { + 'builds': [ + _getArtifactBuildStep(), + for (final TestBundle bundle in config.testBundles) + _getBundleBuildStep(bundle), + ], + 'tests': _getAllTestSteps(config.testSuites) + }; + return const JsonEncoder.withIndent(' ').convert(outputJson); +} + +Map _getArtifactBuildStep() { + return { + 'name': 'web_tests/artifacts', + 'drone_dimensions': [ + 'device_type=none', + 'os=Linux', + 'cores=32' + ], + 'gclient_variables': { + 'download_android_deps': false, + 'download_emsdk': true, + }, + 'gn': [ + '--web', + '--runtime-mode=release', + '--no-goma', + ], + 'ninja': { + 'config': 'wasm_release', + 'targets': [ + 'flutter/web_sdk:flutter_web_sdk_archive' + ] + }, + 'archives': [ + { + 'name': 'wasm_release', + 'base_path': 'out/wasm_release/zip_archives/', + 'type': 'gcs', + 'include_paths': [ + 'out/wasm_release/zip_archives/flutter-web-sdk.zip' + ], + 'realm': 'production', + } + ], + 'generators': { + 'tasks': [ + { + 'name': 'check licenses', + 'parameters': [ + 'check-licenses' + ], + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], + + }, + { + 'name': 'web engine analysis', + 'parameters': [ + 'analyze' + ], + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], + }, + { + 'name': 'copy artifacts for web tests', + 'parameters': [ + 'test', + '--copy-artifacts', + ], + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], + }, + ] + }, + }; +} + +Map _getBundleBuildStep(TestBundle bundle) { + return { + 'name': 'web_tests/test_bundles/${bundle.name}', + 'drone_dimensions': [ + 'device_type=none', + 'os=Linux', + 'cores=32', + ], + 'generators': { + 'tasks': [ + { + 'name': 'compile bundle ${bundle.name}', + 'parameters': [ + 'test', + '--compile', + '--bundle=${bundle.name}', + ], + 'scripts': [ 'flutter/lib/web_ui/dev/felt' ], + } + ] + }, + }; +} + +Iterable _getAllTestSteps(List suites) { + return [ + ..._getTestStepsForPlatform(suites, 'Linux', { + BrowserName.chrome, + BrowserName.firefox, + }), + ..._getTestStepsForPlatform(suites, 'Mac', { + BrowserName.safari, + }), + ..._getTestStepsForPlatform(suites, 'Windows', { + BrowserName.chrome, + }), + ]; +} + +Iterable _getTestStepsForPlatform( + List suites, + String platform, + Set browsers) { + return suites + .where((TestSuite suite) => browsers.contains(suite.runConfig.browser)) + .map((TestSuite suite) => { + 'name': '$platform run ${suite.name} suite', + 'recipe': 'engine_v2/tester_engine', + 'drone_dimensions': [ + 'device_type=none', + 'os=$platform', + ], + 'gclient_variables': { + 'download_android_deps': false, + }, + 'dependencies': [ + 'web_tests/artifacts', + 'web_tests/test_bundles/${suite.testBundle.name}', + ], + 'test_dependencies': [ + { + 'dependency': 'goldctl', + 'version': 'git_revision:3a77d0b12c697a840ca0c7705208e8622dc94603', + }, + if (suite.runConfig.browser == BrowserName.chrome) + { + 'dependency': 'chrome_and_driver', + 'version': 'version:111.0', + }, + if (suite.runConfig.browser == BrowserName.firefox) + { + 'dependency': 'firefox', + 'version': 'version:106.0', + } + ], + 'tasks': [ + { + 'name': 'run suite ${suite.name}', + 'parameters': [ + 'test', + '--run', + '--suite=${suite.name}' + ], + 'script': 'flutter/lib/web_ui/dev/felt', + } + ] + } + ); +} diff --git a/lib/web_ui/dev/licenses.dart b/lib/web_ui/dev/licenses.dart index a15e2757eba6e..8ce8090d628cd 100644 --- a/lib/web_ui/dev/licenses.dart +++ b/lib/web_ui/dev/licenses.dart @@ -82,7 +82,6 @@ class LicensesCommand extends Command { // This is the old path that tests used to be built into. Ignore anything // within this path. final String legacyBuildPath = path.join(environment.webUiRootDir.path, 'build'); - return directory.listSync(recursive: true).whereType().where((io.File f) { if (!f.path.endsWith('.dart') && !f.path.endsWith('.js')) { // Not a source file we're checking. diff --git a/lib/web_ui/dev/pipeline.dart b/lib/web_ui/dev/pipeline.dart index 8a7b286acbefc..e8e8a4d2e7f7b 100644 --- a/lib/web_ui/dev/pipeline.dart +++ b/lib/web_ui/dev/pipeline.dart @@ -8,6 +8,7 @@ import 'dart:io' as io; import 'package:path/path.dart' as path; import 'package:watcher/watcher.dart'; +import 'exceptions.dart'; import 'utils.dart'; /// Describes what [Pipeline] is currently doing. @@ -87,6 +88,13 @@ abstract class ProcessStep implements PipelineStep { } } +class _PipelineStepFailure { + _PipelineStepFailure(this.step, this.error); + + final PipelineStep step; + final Object error; +} + /// Executes a sequence of asynchronous tasks, typically as part of a build/test /// process. /// @@ -112,27 +120,34 @@ class Pipeline { /// /// Returns a future that resolves after all steps have been performed. /// - /// The future resolves to an error as soon as any of the steps fails. + /// If any steps fail, the pipeline attempts to continue to subsequent steps, + /// but will fail at the end. /// /// The pipeline may be interrupted by calling [stop] before the future /// resolves. Future run() async { _status = PipelineStatus.started; - try { - for (final PipelineStep step in steps) { - if (status != PipelineStatus.started) { - break; - } - _currentStep = step; - _currentStepFuture = step.run(); + final List<_PipelineStepFailure> failures = <_PipelineStepFailure>[]; + for (final PipelineStep step in steps) { + _currentStep = step; + _currentStepFuture = step.run(); + try { await _currentStepFuture; + } catch (e) { + failures.add(_PipelineStepFailure(step, e)); + } finally { + _currentStep = null; } + } + if (failures.isEmpty) { _status = PipelineStatus.done; - } catch (_) { + } else { _status = PipelineStatus.error; - rethrow; - } finally { - _currentStep = null; + print('Pipeline experienced the following failures:'); + for (final _PipelineStepFailure failure in failures) { + print(' "${failure.step.description}": ${failure.error}'); + } + throw ToolExit('Test pipeline failed.'); } } diff --git a/lib/web_ui/dev/run.dart b/lib/web_ui/dev/run.dart deleted file mode 100644 index eaed34bfbf80c..0000000000000 --- a/lib/web_ui/dev/run.dart +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2013 The Flutter Authors. 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' as io; - -import 'package:args/command_runner.dart'; - -import 'common.dart'; -import 'pipeline.dart'; -import 'steps/compile_tests_step.dart'; -import 'steps/run_tests_step.dart'; -import 'utils.dart'; - -/// Runs build and test steps. -/// -/// This command is designed to be invoked by the LUCI build graph. However, it -/// is also usable locally. -/// -/// Usage: -/// -/// felt run name_of_build_step -class RunCommand extends Command with ArgUtils { - RunCommand() { - argParser.addFlag( - 'list', - abbr: 'l', - help: 'Lists all available build steps.', - ); - argParser.addFlag( - 'require-skia-gold', - help: 'Whether we require Skia Gold to be available or not. When this ' - 'flag is true, the tests will fail if Skia Gold is not available.', - ); - argParser.addFlag( - 'wasm', - help: 'Whether the test we are running are compiled to webassembly.' - ); - } - - @override - String get name => 'run'; - - bool get isWasm => boolArg('wasm'); - - bool get isListSteps => boolArg('list'); - - /// When running screenshot tests, require Skia Gold to be available and - /// reachable. - bool get requireSkiaGold => boolArg('require-skia-gold'); - - @override - String get description => 'Runs a build step.'; - - /// Build steps to run, in order specified. - List get stepNames => argResults!.rest; - - @override - FutureOr run() async { - // All available build steps. - final Map buildSteps = { - 'compile_tests': CompileTestsStep(), - for (final String browserName in kAllBrowserNames) - 'run_tests_$browserName': RunTestsStep( - browserName: browserName, - isDebug: false, - isWasm: isWasm, - doUpdateScreenshotGoldens: false, - requireSkiaGold: requireSkiaGold, - overridePathToCanvasKit: null, - ), - }; - - if (isListSteps) { - buildSteps.keys.forEach(print); - return true; - } - - if (stepNames.isEmpty) { - throw UsageException('No build steps specified.', argParser.usage); - } - - final List unrecognizedStepNames = []; - for (final String stepName in stepNames) { - if (!buildSteps.containsKey(stepName)) { - unrecognizedStepNames.add(stepName); - } - } - if (unrecognizedStepNames.isNotEmpty) { - io.stderr.writeln( - 'Unknown build steps specified: ${unrecognizedStepNames.join(', ')}', - ); - return false; - } - - final List steps = []; - print('Running steps ${steps.join(', ')}'); - for (final String stepName in stepNames) { - steps.add(buildSteps[stepName]!); - } - - final Stopwatch stopwatch = Stopwatch()..start(); - final Pipeline pipeline = Pipeline(steps: steps); - await pipeline.run(); - stopwatch.stop(); - print('Finished running steps in ${stopwatch.elapsedMilliseconds / 1000} seconds.'); - - return true; - } -} diff --git a/lib/web_ui/dev/steps/compile_bundle_step.dart b/lib/web_ui/dev/steps/compile_bundle_step.dart new file mode 100644 index 0000000000000..16ab5e2310b1f --- /dev/null +++ b/lib/web_ui/dev/steps/compile_bundle_step.dart @@ -0,0 +1,308 @@ +// Copyright 2013 The Flutter Authors. 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' as io; + +import 'package:path/path.dart' as pathlib; +import 'package:pool/pool.dart'; + +import '../environment.dart'; +import '../exceptions.dart'; +import '../felt_config.dart'; +import '../pipeline.dart'; +import '../utils.dart' show AnsiColors, FilePath, ProcessManager, cleanup, getBundleBuildDirectory, startProcess; + +/// Compiles a web test bundle into web_ui/build/test_bundles/. +class CompileBundleStep implements PipelineStep { + CompileBundleStep({ + required this.bundle, + required this.isVerbose, + this.testFiles, + }); + + final TestBundle bundle; + final bool isVerbose; + final Set? testFiles; + + // Maximum number of concurrent compile processes to use. + static final int _compileConcurrency = int.parse(io.Platform.environment['FELT_COMPILE_CONCURRENCY'] ?? '8'); + final Pool compilePool = Pool(_compileConcurrency); + + @override + String get description => 'compile_bundle'; + + @override + bool get isSafeToInterrupt => true; + + @override + Future interrupt() async { + await cleanup(); + } + + io.Directory get testSetDirectory => io.Directory( + pathlib.join(environment.webUiTestDir.path, bundle.testSet.directory) + ); + + io.Directory get outputBundleDirectory => getBundleBuildDirectory(bundle); + + List _findTestFiles() { + final io.Directory testDirectory = testSetDirectory; + if (!testDirectory.existsSync()) { + throw ToolExit('Test directory "${testDirectory.path}" for bundle ${bundle.name.ansiMagenta} does not exist.'); + } + return testDirectory + .listSync(recursive: true) + .whereType() + .where((io.File f) => f.path.endsWith('_test.dart')) + .map((io.File f) => FilePath.fromWebUi( + pathlib.relative(f.path, from: environment.webUiRootDir.path))) + .toList(); + } + + TestCompiler _createCompiler() { + switch (bundle.compileConfig.compiler) { + case Compiler.dart2js: + return Dart2JSCompiler( + testSetDirectory, + outputBundleDirectory, + renderer: bundle.compileConfig.renderer, + isVerbose: isVerbose, + ); + case Compiler.dart2wasm: + return Dart2WasmCompiler( + testSetDirectory, + outputBundleDirectory, + renderer: bundle.compileConfig.renderer, + isVerbose: isVerbose, + ); + } + } + + @override + Future run() async { + print('Compiling test bundle ${bundle.name.ansiMagenta}...'); + final List allTests = _findTestFiles(); + final TestCompiler compiler = _createCompiler(); + final Stopwatch stopwatch = Stopwatch()..start(); + final String testSetDirectoryPath = testSetDirectory.path; + + // Clear out old bundle compilations, if they exist + if (outputBundleDirectory.existsSync()) { + outputBundleDirectory.deleteSync(recursive: true ); + } + + final List>> pendingResults = >>[]; + for (final FilePath testFile in allTests) { + final String relativePath = pathlib.relative( + testFile.absolute, + from: testSetDirectoryPath); + final Future> result = compilePool.withResource(() async { + if (testFiles != null && !testFiles!.contains(testFile)) { + return MapEntry(relativePath, CompileResult.filtered); + } + final bool success = await compiler.compileTest(testFile); + const int maxTestNameLength = 80; + final String truncatedPath = relativePath.length > maxTestNameLength + ? relativePath.replaceRange(maxTestNameLength - 3, relativePath.length, '...') + : relativePath; + final String expandedPath = truncatedPath.padRight(maxTestNameLength); + io.stdout.write('\r ${success ? expandedPath.ansiGreen : expandedPath.ansiRed}'); + return success + ? MapEntry(relativePath, CompileResult.success) + : MapEntry(relativePath, CompileResult.compilationFailure); + }); + pendingResults.add(result); + } + final Map results = Map.fromEntries(await Future.wait(pendingResults)); + stopwatch.stop(); + + final String resultsJson = const JsonEncoder.withIndent(' ').convert({ + 'name': bundle.name, + 'directory': bundle.testSet.directory, + 'compiler': bundle.compileConfig.compiler.name, + 'renderer': bundle.compileConfig.renderer.name, + 'compileTimeInMs': stopwatch.elapsedMilliseconds, + 'results': results.map((String k, CompileResult v) => MapEntry(k, v.name)), + }); + final io.File outputResultsFile = io.File(pathlib.join( + outputBundleDirectory.path, + 'results.json', + )); + outputResultsFile.writeAsStringSync(resultsJson); + final List failedFiles = []; + results.forEach((String fileName, CompileResult result) { + if (result == CompileResult.compilationFailure) { + failedFiles.add(fileName); + } + }); + if (failedFiles.isEmpty) { + print('\rCompleted compilation of ${bundle.name.ansiMagenta} in ${stopwatch.elapsedMilliseconds}ms.'.padRight(82)); + } else { + print('\rThe bundle ${bundle.name.ansiMagenta} compiled with some failures in ${stopwatch.elapsedMilliseconds}ms.'); + print('Compilation failures:'); + for (final String fileName in failedFiles) { + print(' $fileName'); + } + throw ToolExit('Failed to compile ${bundle.name.ansiMagenta}.'); + } + } +} + +enum CompileResult { + success, + compilationFailure, + filtered, +} + +abstract class TestCompiler { + TestCompiler( + this.inputTestSetDirectory, + this.outputTestBundleDirectory, + { + required this.renderer, + required this.isVerbose, + } + ); + + final io.Directory inputTestSetDirectory; + final io.Directory outputTestBundleDirectory; + final Renderer renderer; + final bool isVerbose; + + Future compileTest(FilePath input); +} + +class Dart2JSCompiler extends TestCompiler { + Dart2JSCompiler( + super.inputTestSetDirectory, + super.outputTestBundleDirectory, + { + required super.renderer, + required super.isVerbose, + } + ); + + @override + Future compileTest(FilePath input) async { + final String relativePath = pathlib.relative( + input.absolute, + from: inputTestSetDirectory.path + ); + + final String targetFileName = pathlib.join( + outputTestBundleDirectory.path, + '$relativePath.browser_test.dart.js', + ); + + final io.Directory outputDirectory = io.File(targetFileName).parent; + if (!outputDirectory.existsSync()) { + outputDirectory.createSync(recursive: true); + } + + final List arguments = [ + 'compile', + 'js', + '--no-minify', + '--disable-inlining', + '--enable-asserts', + + // We do not want to auto-select a renderer in tests. As of today, tests + // are designed to run in one specific mode. So instead, we specify the + // renderer explicitly. + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvaskit}', + '-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}', + + '-O2', + '-o', + targetFileName, // target path. + relativePath, // current path. + ]; + + final ProcessManager process = await startProcess( + environment.dartExecutable, + arguments, + workingDirectory: inputTestSetDirectory.path, + failureIsSuccess: true, + evalOutput: !isVerbose, + ); + final int exitCode = await process.wait(); + if (exitCode != 0) { + io.stderr.writeln('ERROR: Failed to compile test $input. ' + 'Dart2js exited with exit code $exitCode'); + return false; + } else { + return true; + } + } +} + +class Dart2WasmCompiler extends TestCompiler { + Dart2WasmCompiler( + super.inputTestSetDirectory, + super.outputTestBundleDirectory, + { + required super.renderer, + required super.isVerbose, + } + ); + + @override + Future compileTest(FilePath input) async { + final String relativePath = pathlib.relative( + input.absolute, + from: inputTestSetDirectory.path + ); + + final String targetFileName = pathlib.join( + outputTestBundleDirectory.path, + '$relativePath.browser_test.dart.wasm', + ); + + final io.Directory outputDirectory = io.File(targetFileName).parent; + if (!outputDirectory.existsSync()) { + outputDirectory.createSync(recursive: true); + } + + final List arguments = [ + environment.dart2wasmSnapshotPath, + + '--dart-sdk=${environment.dartSdkDir.path}', + '--enable-asserts', + + // We do not want to auto-select a renderer in tests. As of today, tests + // are designed to run in one specific mode. So instead, we specify the + // renderer explicitly. + '-DFLUTTER_WEB_AUTO_DETECT=false', + '-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvaskit}', + '-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}', + + if (renderer == Renderer.skwasm) ...[ + '--import-shared-memory', + '--shared-memory-max-pages=32768', + ], + + relativePath, // current path. + targetFileName, // target path. + ]; + + final ProcessManager process = await startProcess( + environment.dartAotRuntimePath, + arguments, + workingDirectory: inputTestSetDirectory.path, + failureIsSuccess: true, + evalOutput: true, + ); + final int exitCode = await process.wait(); + + if (exitCode != 0) { + io.stderr.writeln('ERROR: Failed to compile test $input. ' + 'dart2wasm exited with exit code $exitCode'); + return false; + } else { + return true; + } + } +} diff --git a/lib/web_ui/dev/steps/compile_tests_step.dart b/lib/web_ui/dev/steps/compile_tests_step.dart deleted file mode 100644 index 54abf19191ed8..0000000000000 --- a/lib/web_ui/dev/steps/compile_tests_step.dart +++ /dev/null @@ -1,504 +0,0 @@ -// Copyright 2013 The Flutter Authors. 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' show JsonEncoder; -import 'dart:io' as io; - -import 'package:path/path.dart' as pathlib; -import 'package:pool/pool.dart'; - -import '../environment.dart'; -import '../exceptions.dart'; -import '../pipeline.dart'; -import '../utils.dart'; - -/// Compiles web tests and their dependencies into web_ui/build/. -/// -/// Outputs of this step: -/// -/// * canvaskit/ - CanvasKit artifacts -/// * assets/ - test fonts -/// * host/ - compiled test host page and static artifacts -/// * test/ - compiled test code -/// * test_images/ - test images copied from Skis sources. -class CompileTestsStep implements PipelineStep { - CompileTestsStep({ - this.testFiles, - this.useLocalCanvasKit = false, - this.isWasm = false - }); - - final List? testFiles; - final bool isWasm; - - final bool useLocalCanvasKit; - - @override - String get description => 'compile_tests'; - - @override - bool get isSafeToInterrupt => true; - - @override - Future interrupt() async { - await cleanup(); - } - - @override - Future run() async { - await environment.webUiBuildDir.create(recursive: true); - if (isWasm) { - await copyDart2WasmTestScript(); - await copySkwasm(); - } - await copyCanvasKitFiles(useLocalCanvasKit: useLocalCanvasKit); - await buildHostPage(); - await copyTestFonts(); - await copySkiaTestImages(); - await compileTests(testFiles ?? findAllTests(), isWasm); - } -} - -const Map _kTestFonts = { - 'Ahem': 'ahem.ttf', - 'Roboto': 'Roboto-Regular.ttf', - 'RobotoVariable': 'RobotoSlab-VariableFont_wght.ttf', - 'Noto Naskh Arabic UI': 'NotoNaskhArabic-Regular.ttf', - 'Noto Color Emoji': 'NotoColorEmoji.ttf', -}; - -Future copyTestFonts() async { - final String fontsPath = pathlib.join( - environment.flutterDirectory.path, - 'third_party', - 'txt', - 'third_party', - 'fonts', - ); - - final List fontManifest = []; - for (final MapEntry fontEntry in _kTestFonts.entries) { - final String family = fontEntry.key; - final String fontFile = fontEntry.value; - - fontManifest.add({ - 'family': family, - 'fonts': [ - { - 'asset': 'fonts/$fontFile', - }, - ], - }); - - final io.File sourceTtf = io.File(pathlib.join(fontsPath, fontFile)); - final io.File destinationTtf = io.File(pathlib.join( - environment.webUiBuildDir.path, - 'assets', - 'fonts', - fontFile, - )); - await destinationTtf.create(recursive: true); - await sourceTtf.copy(destinationTtf.path); - } - - final io.File fontManifestFile = io.File(pathlib.join( - environment.webUiBuildDir.path, - 'assets', - 'FontManifest.json', - )); - await fontManifestFile.create(recursive: true); - await fontManifestFile.writeAsString( - const JsonEncoder.withIndent(' ').convert(fontManifest), - ); -} - -Future copySkiaTestImages() async { - final io.Directory testImagesDir = io.Directory(pathlib.join( - environment.engineSrcDir.path, - 'third_party', - 'skia', - 'resources', - 'images', - )); - - for (final io.File imageFile in testImagesDir.listSync(recursive: true).whereType()) { - final io.File destination = io.File(pathlib.join( - environment.webUiBuildDir.path, - 'test_images', - pathlib.relative(imageFile.path, from: testImagesDir.path), - )); - destination.createSync(recursive: true); - await imageFile.copy(destination.path); - } -} - -Future copyDart2WasmTestScript() async { - final io.File sourceFile = io.File(pathlib.join( - environment.webUiDevDir.path, - 'test_dart2wasm.js', - )); - final io.File targetFile = io.File(pathlib.join( - environment.webUiBuildDir.path, - 'test_dart2wasm.js', - )); - await sourceFile.copy(targetFile.path); -} - -Future copySkwasm() async { - final io.Directory targetDir = io.Directory(pathlib.join( - environment.webUiBuildDir.path, - 'skwasm', - )); - - await targetDir.create(recursive: true); - - for (final String fileName in [ - 'skwasm.wasm', - 'skwasm.js', - 'skwasm.worker.js', - ]) { - final io.File sourceFile = io.File(pathlib.join( - environment.wasmReleaseOutDir.path, - fileName, - )); - final io.File targetFile = io.File(pathlib.join( - targetDir.path, - fileName, - )); - await sourceFile.copy(targetFile.path); - } -} - -final io.Directory _localCanvasKitDir = io.Directory(pathlib.join( - environment.wasmReleaseOutDir.path, - 'canvaskit', -)); -final io.File _localCanvasKitWasm = io.File(pathlib.join( - _localCanvasKitDir.path, - 'canvaskit.wasm', -)); - -Future copyCanvasKitFiles({bool useLocalCanvasKit = false}) async { - // If CanvasKit has been built locally, use that instead of the CIPD version. - final bool localCanvasKitExists = _localCanvasKitWasm.existsSync(); - if (useLocalCanvasKit && !localCanvasKitExists) { - throw ArgumentError('Requested to use local CanvasKit but could not find the ' - 'built CanvasKit at ${_localCanvasKitWasm.path}. Falling back to ' - 'CanvasKit from CIPD.'); - } - - final io.Directory targetDir = io.Directory(pathlib.join( - environment.webUiBuildDir.path, - 'canvaskit', - )); - - if (useLocalCanvasKit) { - final Iterable canvasKitFiles = - _localCanvasKitDir.listSync(recursive: true).whereType(); - for (final io.File file in canvasKitFiles) { - if (!file.path.endsWith('.wasm') && !file.path.endsWith('.js')) { - // We only need the .wasm and .js files. - continue; - } - final String relativePath = - pathlib.relative(file.path, from: _localCanvasKitDir.path); - final io.File normalTargetFile = - io.File(pathlib.join(targetDir.path, relativePath)); - await normalTargetFile.create(recursive: true); - await file.copy(normalTargetFile.path); - } - } else { - final io.Directory canvasKitDir = io.Directory(pathlib.join( - environment.engineSrcDir.path, - 'third_party', - 'web_dependencies', - 'canvaskit', - )); - - final Iterable canvasKitFiles = canvasKitDir - .listSync(recursive: true) - .whereType(); - - for (final io.File file in canvasKitFiles) { - final String relativePath = - pathlib.relative(file.path, from: canvasKitDir.path); - final io.File targetFile = io.File(pathlib.join( - targetDir.path, - relativePath, - )); - await targetFile.create(recursive: true); - await file.copy(targetFile.path); - } - } -} - -/// Compiles the specified unit tests. -Future compileTests(List testFiles, bool isWasm) async { - final Stopwatch stopwatch = Stopwatch()..start(); - - final TestsByRenderer sortedTests = sortTestsByRenderer(testFiles, isWasm); - - await Future.wait(>[ - if (sortedTests.htmlTests.isNotEmpty) - _compileTestsInParallel(targets: sortedTests.htmlTests, isWasm: isWasm), - if (sortedTests.canvasKitTests.isNotEmpty) - _compileTestsInParallel(targets: sortedTests.canvasKitTests, renderer: Renderer.canvasKit, isWasm: isWasm), - if (sortedTests.skwasmTests.isNotEmpty) - _compileTestsInParallel(targets: sortedTests.skwasmTests, renderer: Renderer.skwasm, isWasm: isWasm), - ]); - - stopwatch.stop(); - - final int targetCount = sortedTests.numTargetsToCompile; - print( - 'Built $targetCount tests in ${stopwatch.elapsedMilliseconds ~/ 1000} ' - 'seconds using $_dart2jsConcurrency concurrent compile processes.', - ); -} - -// Maximum number of concurrent dart2js processes to use. -int _dart2jsConcurrency = int.parse(io.Platform.environment['FELT_DART2JS_CONCURRENCY'] ?? '8'); - -final Pool _dart2jsPool = Pool(_dart2jsConcurrency); - -/// Spawns multiple dart2js processes to compile [targets] in parallel. -Future _compileTestsInParallel({ - required List targets, - Renderer renderer = Renderer.html, - bool isWasm = false, -}) async { - final Stream results = _dart2jsPool.forEach( - targets, - (FilePath file) => compileUnitTest(file, renderer: renderer, isWasm: isWasm), - ); - await for (final bool isSuccess in results) { - if (!isSuccess) { - throw ToolExit('Failed to compile tests.'); - } - } -} - -Future compileUnitTest(FilePath input, {required Renderer renderer, required bool isWasm}) async { - return isWasm ? compileUnitTestToWasm(input, renderer: renderer) - : compileUnitTestToJS(input, renderer: renderer); -} - -/// Compiles one unit test using `dart2js`. -/// -/// When building for CanvasKit we have to use extra argument -/// `DFLUTTER_WEB_USE_SKIA=true`. -/// -/// Dart2js creates the following outputs: -/// - target.browser_test.dart.js -/// - target.browser_test.dart.js.deps -/// - target.browser_test.dart.js.map -/// under the same directory with test file. If all these files are not in -/// the same directory, Chrome dev tools cannot load the source code during -/// debug. -/// -/// All the files under test already copied from /test directory to /build -/// directory before test are build. See [_copyFilesFromTestToBuild]. -/// -/// Later the extra files will be deleted in [_cleanupExtraFilesUnderTestDir]. -Future compileUnitTestToJS(FilePath input, {required Renderer renderer}) async { - // Compile to different directories for different renderers. This allows us - // to run the same test in multiple renderers. - final String targetFileName = pathlib.join( - environment.webUiBuildDir.path, - getBuildDirForRenderer(renderer), - '${input.relativeToWebUi}.browser_test.dart.js', - ); - - final io.Directory directoryToTarget = io.Directory(pathlib.join( - environment.webUiBuildDir.path, - getBuildDirForRenderer(renderer), - pathlib.dirname(input.relativeToWebUi))); - - if (!directoryToTarget.existsSync()) { - directoryToTarget.createSync(recursive: true); - } - - final List arguments = [ - 'compile', - 'js', - '--no-minify', - '--disable-inlining', - '--enable-asserts', - - // We do not want to auto-select a renderer in tests. As of today, tests - // are designed to run in one specific mode. So instead, we specify the - // renderer explicitly. - '-DFLUTTER_WEB_AUTO_DETECT=false', - '-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvasKit}', - '-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}', - - '-O2', - '-o', - targetFileName, // target path. - input.relativeToWebUi, // current path. - ]; - - final int exitCode = await runProcess( - environment.dartExecutable, - arguments, - workingDirectory: environment.webUiRootDir.path, - ); - - if (exitCode != 0) { - io.stderr.writeln('ERROR: Failed to compile test $input. ' - 'Dart2js exited with exit code $exitCode'); - return false; - } else { - return true; - } -} - -Future compileUnitTestToWasm(FilePath input, {required Renderer renderer}) async { - final String targetFileName = pathlib.join( - environment.webUiBuildDir.path, - getBuildDirForRenderer(renderer), - '${input.relativeToWebUi}.browser_test.dart.wasm', - ); - - final io.Directory directoryToTarget = io.Directory(pathlib.join( - environment.webUiBuildDir.path, - getBuildDirForRenderer(renderer), - pathlib.dirname(input.relativeToWebUi))); - - if (!directoryToTarget.existsSync()) { - directoryToTarget.createSync(recursive: true); - } - - final List arguments = [ - environment.dart2wasmSnapshotPath, - - '--dart-sdk=${environment.dartSdkDir.path}', - '--enable-asserts', - - // We do not want to auto-select a renderer in tests. As of today, tests - // are designed to run in one specific mode. So instead, we specify the - // renderer explicitly. - '-DFLUTTER_WEB_AUTO_DETECT=false', - '-DFLUTTER_WEB_USE_SKIA=${renderer == Renderer.canvasKit}', - '-DFLUTTER_WEB_USE_SKWASM=${renderer == Renderer.skwasm}', - - if (renderer == Renderer.skwasm) - ...[ - '--import-shared-memory', - '--shared-memory-max-pages=32768', - ], - - input.relativeToWebUi, // current path. - targetFileName, // target path. - ]; - - final int exitCode = await runProcess( - environment.dartAotRuntimePath, - arguments, - workingDirectory: environment.webUiRootDir.path, - ); - - if (exitCode != 0) { - io.stderr.writeln('ERROR: Failed to compile test $input. ' - 'dart2wasm exited with exit code $exitCode'); - return false; - } else { - return true; - } -} - -Future buildHostPage() async { - final String hostDartPath = pathlib.join('lib', 'static', 'host.dart'); - final io.File hostDartFile = io.File(pathlib.join( - environment.webEngineTesterRootDir.path, - hostDartPath, - )); - final String targetDirectoryPath = pathlib.join( - environment.webUiBuildDir.path, - 'host', - ); - io.Directory(targetDirectoryPath).createSync(recursive: true); - final String targetFilePath = pathlib.join( - targetDirectoryPath, - 'host.dart', - ); - - const List staticFiles = [ - 'favicon.ico', - 'host.css', - 'index.html', - ]; - for (final String staticFilePath in staticFiles) { - final io.File source = io.File(pathlib.join( - environment.webEngineTesterRootDir.path, - 'lib', - 'static', - staticFilePath, - )); - final io.File destination = io.File(pathlib.join( - targetDirectoryPath, - staticFilePath, - )); - await source.copy(destination.path); - } - - final io.File timestampFile = io.File(pathlib.join( - environment.webEngineTesterRootDir.path, - '$targetFilePath.js.timestamp', - )); - - final String timestamp = - hostDartFile.statSync().modified.millisecondsSinceEpoch.toString(); - if (timestampFile.existsSync()) { - final String lastBuildTimestamp = timestampFile.readAsStringSync(); - if (lastBuildTimestamp == timestamp) { - // The file is still fresh. No need to rebuild. - return; - } else { - // Record new timestamp, but don't return. We need to rebuild. - print('${hostDartFile.path} timestamp changed. Rebuilding.'); - } - } else { - print('Building ${hostDartFile.path}.'); - } - - int exitCode = await runProcess( - environment.dartExecutable, - [ - 'pub', - 'get', - ], - workingDirectory: environment.webEngineTesterRootDir.path - ); - - if (exitCode != 0) { - throw ToolExit( - 'Failed to run pub get for web_engine_tester, exit code $exitCode', - exitCode: exitCode, - ); - } - - exitCode = await runProcess( - environment.dartExecutable, - [ - 'compile', - 'js', - hostDartPath, - '-o', - '$targetFilePath.js', - ], - workingDirectory: environment.webEngineTesterRootDir.path, - ); - - if (exitCode != 0) { - throw ToolExit( - 'Failed to compile ${hostDartFile.path}. Compiler ' - 'exited with exit code $exitCode', - exitCode: exitCode, - ); - } - - // Record the timestamp to avoid rebuilding unless the file changes. - timestampFile.writeAsStringSync(timestamp); -} diff --git a/lib/web_ui/dev/steps/copy_artifacts_step.dart b/lib/web_ui/dev/steps/copy_artifacts_step.dart new file mode 100644 index 0000000000000..25c9af656ea11 --- /dev/null +++ b/lib/web_ui/dev/steps/copy_artifacts_step.dart @@ -0,0 +1,293 @@ +// Copyright 2013 The Flutter Authors. 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' show JsonEncoder; +import 'dart:io' as io; + +import 'package:path/path.dart' as pathlib; + +import '../environment.dart'; +import '../exceptions.dart'; +import '../felt_config.dart'; +import '../pipeline.dart'; +import '../utils.dart'; + +class CopyArtifactsStep implements PipelineStep { + CopyArtifactsStep(this.artifactDeps, { required this.isProfile }); + + final ArtifactDependencies artifactDeps; + final bool isProfile; + + @override + String get description => 'copy_artifacts'; + + @override + bool get isSafeToInterrupt => true; + + @override + Future interrupt() async { + await cleanup(); + } + + @override + Future run() async { + await environment.webTestsArtifactsDir.create(recursive: true); + await copyDart2WasmTestScript(); + await buildHostPage(); + await copyTestFonts(); + await copySkiaTestImages(); + if (artifactDeps.canvasKit) { + print('Copying CanvasKit...'); + await copyCanvasKitFiles('canvaskit', 'canvaskit'); + } + if (artifactDeps.canvasKitChromium) { + print('Copying CanvasKit (Chromium)...'); + await copyCanvasKitFiles('canvaskit_chromium', 'canvaskit/chromium'); + } + if (artifactDeps.skwasm) { + print('Copying Skwasm...'); + await copySkwasm(); + } + } + + Future copyDart2WasmTestScript() async { + final io.File sourceFile = io.File(pathlib.join( + environment.webUiDevDir.path, + 'test_dart2wasm.js', + )); + final io.File targetFile = io.File(pathlib.join( + environment.webTestsArtifactsDir.path, + 'test_dart2wasm.js', + )); + await sourceFile.copy(targetFile.path); + } + + Future copyTestFonts() async { + const Map testFonts = { + 'Ahem': 'ahem.ttf', + 'Roboto': 'Roboto-Regular.ttf', + 'RobotoVariable': 'RobotoSlab-VariableFont_wght.ttf', + 'Noto Naskh Arabic UI': 'NotoNaskhArabic-Regular.ttf', + 'Noto Color Emoji': 'NotoColorEmoji.ttf', + }; + + final String fontsPath = pathlib.join( + environment.flutterDirectory.path, + 'third_party', + 'txt', + 'third_party', + 'fonts', + ); + + final List fontManifest = []; + for (final MapEntry fontEntry in testFonts.entries) { + final String family = fontEntry.key; + final String fontFile = fontEntry.value; + + fontManifest.add({ + 'family': family, + 'fonts': [ + { + 'asset': 'fonts/$fontFile', + }, + ], + }); + + final io.File sourceTtf = io.File(pathlib.join(fontsPath, fontFile)); + final io.File destinationTtf = io.File(pathlib.join( + environment.webTestsArtifactsDir.path, + 'assets', + 'fonts', + fontFile, + )); + await destinationTtf.create(recursive: true); + await sourceTtf.copy(destinationTtf.path); + } + + final io.File fontManifestFile = io.File(pathlib.join( + environment.webTestsArtifactsDir.path, + 'assets', + 'FontManifest.json', + )); + await fontManifestFile.create(recursive: true); + await fontManifestFile.writeAsString( + const JsonEncoder.withIndent(' ').convert(fontManifest), + ); + } + + Future copySkiaTestImages() async { + final io.Directory testImagesDir = io.Directory(pathlib.join( + environment.engineSrcDir.path, + 'third_party', + 'skia', + 'resources', + 'images', + )); + + for (final io.File imageFile in testImagesDir.listSync(recursive: true).whereType()) { + final io.File destination = io.File(pathlib.join( + environment.webTestsArtifactsDir.path, + 'test_images', + pathlib.relative(imageFile.path, from: testImagesDir.path), + )); + destination.createSync(recursive: true); + await imageFile.copy(destination.path); + } + } + + Future copyCanvasKitFiles(String sourcePath, String destinationPath) async { + final String sourceDirectoryPath = pathlib.join( + outBuildPath, + sourcePath, + ); + + final String targetDirectoryPath = pathlib.join( + environment.webTestsArtifactsDir.path, + destinationPath, + ); + + for (final String filename in [ + 'canvaskit.js', + 'canvaskit.wasm', + ]) { + final io.File sourceFile = io.File(pathlib.join( + sourceDirectoryPath, + filename, + )); + final io.File targetFile = io.File(pathlib.join( + targetDirectoryPath, + filename, + )); + if (!sourceFile.existsSync()) { + throw ToolExit('Built CanvasKit artifact not found at path "$sourceFile".'); + } + await targetFile.create(recursive: true); + await sourceFile.copy(targetFile.path); + } + } + + String get outBuildPath => isProfile + ? environment.wasmProfileOutDir.path + : environment.wasmReleaseOutDir.path; + + Future copySkwasm() async { + final io.Directory targetDir = io.Directory(pathlib.join( + environment.webTestsArtifactsDir.path, + 'canvaskit', + )); + + await targetDir.create(recursive: true); + + for (final String fileName in [ + 'skwasm.wasm', + 'skwasm.js', + 'skwasm.worker.js', + ]) { + final io.File sourceFile = io.File(pathlib.join( + outBuildPath, + fileName, + )); + final io.File targetFile = io.File(pathlib.join( + targetDir.path, + fileName, + )); + await sourceFile.copy(targetFile.path); + } + } + + Future buildHostPage() async { + final String hostDartPath = pathlib.join('lib', 'static', 'host.dart'); + final io.File hostDartFile = io.File(pathlib.join( + environment.webEngineTesterRootDir.path, + hostDartPath, + )); + final String targetDirectoryPath = pathlib.join( + environment.webTestsArtifactsDir.path, + 'host', + ); + io.Directory(targetDirectoryPath).createSync(recursive: true); + final String targetFilePath = pathlib.join( + targetDirectoryPath, + 'host.dart', + ); + + const List staticFiles = [ + 'favicon.ico', + 'host.css', + 'index.html', + ]; + for (final String staticFilePath in staticFiles) { + final io.File source = io.File(pathlib.join( + environment.webEngineTesterRootDir.path, + 'lib', + 'static', + staticFilePath, + )); + final io.File destination = io.File(pathlib.join( + targetDirectoryPath, + staticFilePath, + )); + await source.copy(destination.path); + } + + final io.File timestampFile = io.File(pathlib.join( + environment.webEngineTesterRootDir.path, + '$targetFilePath.js.timestamp', + )); + + final String timestamp = + hostDartFile.statSync().modified.millisecondsSinceEpoch.toString(); + if (timestampFile.existsSync()) { + final String lastBuildTimestamp = timestampFile.readAsStringSync(); + if (lastBuildTimestamp == timestamp) { + // The file is still fresh. No need to rebuild. + return; + } else { + // Record new timestamp, but don't return. We need to rebuild. + print('${hostDartFile.path} timestamp changed. Rebuilding.'); + } + } else { + print('Building ${hostDartFile.path}.'); + } + + int exitCode = await runProcess( + environment.dartExecutable, + [ + 'pub', + 'get', + ], + workingDirectory: environment.webEngineTesterRootDir.path + ); + + if (exitCode != 0) { + throw ToolExit( + 'Failed to run pub get for web_engine_tester, exit code $exitCode', + exitCode: exitCode, + ); + } + + exitCode = await runProcess( + environment.dartExecutable, + [ + 'compile', + 'js', + hostDartPath, + '-o', + '$targetFilePath.js', + ], + workingDirectory: environment.webEngineTesterRootDir.path, + ); + + if (exitCode != 0) { + throw ToolExit( + 'Failed to compile ${hostDartFile.path}. Compiler ' + 'exited with exit code $exitCode', + exitCode: exitCode, + ); + } + + // Record the timestamp to avoid rebuilding unless the file changes. + timestampFile.writeAsStringSync(timestamp); + } +} diff --git a/lib/web_ui/dev/steps/run_suite_step.dart b/lib/web_ui/dev/steps/run_suite_step.dart new file mode 100644 index 0000000000000..3650670590bff --- /dev/null +++ b/lib/web_ui/dev/steps/run_suite_step.dart @@ -0,0 +1,216 @@ +// Copyright 2013 The Flutter Authors. 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' as io; + +import 'package:path/path.dart' as pathlib; +// TODO(yjbanov): remove hacks when this is fixed: +// https://github.com/dart-lang/test/issues/1521 +import 'package:skia_gold_client/skia_gold_client.dart'; +import 'package:test_api/src/backend/runtime.dart' as hack; +import 'package:test_core/src/executable.dart' as test; +import 'package:test_core/src/runner/hack_register_platform.dart' as hack; + +import '../browser.dart'; +import '../common.dart'; +import '../environment.dart'; +import '../exceptions.dart'; +import '../felt_config.dart'; +import '../pipeline.dart'; +import '../test_platform.dart'; +import '../utils.dart'; + +/// Runs a test suite. +/// +/// Assumes the artifacts from previous steps are available, either from +/// running them prior to this step locally, or by having the build graph copy +/// them from another bot. +class RunSuiteStep implements PipelineStep { + RunSuiteStep(this.suite, { + required this.isDebug, + required this.isVerbose, + required this.doUpdateScreenshotGoldens, + required this.requireSkiaGold, + this.testFiles, + required this.overridePathToCanvasKit, + }); + + final TestSuite suite; + final Set? testFiles; + final bool isDebug; + final bool isVerbose; + final bool doUpdateScreenshotGoldens; + final String? overridePathToCanvasKit; + + /// Require Skia Gold to be available and reachable. + final bool requireSkiaGold; + + bool get isWasm => suite.testBundle.compileConfig.compiler == Compiler.dart2wasm; + + @override + String get description => 'run_suite'; + + @override + bool get isSafeToInterrupt => true; + + @override + Future interrupt() async {} + + @override + Future run() async { + _prepareTestResultsDirectory(); + final BrowserEnvironment browserEnvironment = getBrowserEnvironment( + suite.runConfig.browser, + enableWasmGC: isWasm); + await browserEnvironment.prepare(); + + final SkiaGoldClient? skiaClient = await _createSkiaClient(); + final String configurationFilePath = pathlib.join( + environment.webUiRootDir.path, + browserEnvironment.packageTestConfigurationYamlFile, + ); + final String bundleBuildPath = getBundleBuildDirectory(suite.testBundle).path; + final List testArgs = [ + ...['-r', 'compact'], + // Disable concurrency. Running with concurrency proved to be flaky. + '--concurrency=1', + if (isDebug) '--pause-after-load', + '--platform=${browserEnvironment.packageTestRuntime.identifier}', + '--precompiled=$bundleBuildPath', + '--configuration=$configurationFilePath', + '--', + ..._collectTestPaths(), + ]; + + hack.registerPlatformPlugin([ + browserEnvironment.packageTestRuntime, + ], () { + return BrowserPlatform.start( + suite, + browserEnvironment: browserEnvironment, + doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, + skiaClient: skiaClient, + overridePathToCanvasKit: overridePathToCanvasKit, + isWasm: isWasm, + isVerbose: isVerbose, + ); + }); + + print('[${suite.name.ansiCyan}] Running...'); + + // We want to run tests with the test set's directory as a working directory. + final io.Directory testSetDirectory = io.Directory(pathlib.join( + environment.webUiTestDir.path, + suite.testBundle.testSet.directory, + )); + final dynamic originalCwd = io.Directory.current; + io.Directory.current = testSetDirectory; + try { + await test.main(testArgs); + } finally { + io.Directory.current = originalCwd; + } + + await browserEnvironment.cleanup(); + + if (io.exitCode != 0) { + print('[${suite.name.ansiCyan}] ${'Some tests failed.'.ansiRed}'); + io.exitCode = 0; + } else { + print('[${suite.name.ansiCyan}] ${'All tests passed!'.ansiGreen}'); + } + } + + io.Directory _prepareTestResultsDirectory() { + final io.Directory resultsDirectory = io.Directory(pathlib.join( + environment.webUiTestResultsDirectory.path, + suite.name, + )); + if (resultsDirectory.existsSync()) { + resultsDirectory.deleteSync(recursive: true); + } + resultsDirectory.createSync(recursive: true); + return resultsDirectory; + } + + List _collectTestPaths() { + final io.Directory bundleBuild = getBundleBuildDirectory(suite.testBundle); + final io.File resultsJsonFile = io.File(pathlib.join( + bundleBuild.path, + 'results.json', + )); + if (!resultsJsonFile.existsSync()) { + throw ToolExit('Could not find built bundle ${suite.testBundle.name.ansiMagenta} for suite ${suite.name.ansiCyan}.'); + } + final String jsonString = resultsJsonFile.readAsStringSync(); + final dynamic jsonContents = const JsonDecoder().convert(jsonString); + final dynamic results = jsonContents['results']; + final List testPaths = []; + results.forEach((dynamic k, dynamic v) { + final String result = v as String; + final String testPath = k as String; + if (testFiles != null) { + if (!testFiles!.contains(FilePath.fromTestSet(suite.testBundle.testSet, testPath))) { + return; + } + } + if (result == 'success') { + testPaths.add(testPath); + } + }); + return testPaths; + } + + Future _createSkiaClient() async { + final Renderer renderer = suite.testBundle.compileConfig.renderer; + final CanvasKitVariant? variant = suite.runConfig.variant; + final SkiaGoldClient skiaClient = SkiaGoldClient( + environment.webUiSkiaGoldDirectory, + dimensions: { + 'Browser': suite.runConfig.browser.name, + if (isWasm) 'Wasm': 'true', + 'Renderer': renderer.name, + if (variant != null) 'CanvasKitVariant': variant.name, + }, + ); + + if (await _checkSkiaClient(skiaClient)) { + return skiaClient; + } + + if (requireSkiaGold) { + throw ToolExit('Skia Gold is required but is unavailable.'); + } + + return null; + } + + /// Checks whether the Skia Client is usable in this environment. + Future _checkSkiaClient(SkiaGoldClient skiaClient) async { + // Now let's check whether Skia Gold is reachable or not. + if (isLuci) { + if (isSkiaGoldClientAvailable) { + try { + await skiaClient.auth(); + return true; + } catch (e) { + print(e); + } + } + } else { + try { + // Check if we can reach Gold. + await skiaClient.getExpectationForTest(''); + return true; + } on io.OSError catch (_) { + print('OSError occurred, could not reach Gold.'); + } on io.SocketException catch (_) { + print('SocketException occurred, could not reach Gold.'); + } + } + + return false; + } +} diff --git a/lib/web_ui/dev/steps/run_tests_step.dart b/lib/web_ui/dev/steps/run_tests_step.dart deleted file mode 100644 index 196a5617d2a97..0000000000000 --- a/lib/web_ui/dev/steps/run_tests_step.dart +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2013 The Flutter Authors. 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' as io; - -import 'package:path/path.dart' as pathlib; -// TODO(yjbanov): remove hacks when this is fixed: -// https://github.com/dart-lang/test/issues/1521 -import 'package:skia_gold_client/skia_gold_client.dart'; -import 'package:test_api/src/backend/group.dart' as hack; -import 'package:test_api/src/backend/live_test.dart' as hack; -import 'package:test_api/src/backend/runtime.dart' as hack; -import 'package:test_core/src/executable.dart' as test; -import 'package:test_core/src/runner/configuration/reporters.dart' as hack; -import 'package:test_core/src/runner/engine.dart' as hack; -import 'package:test_core/src/runner/hack_register_platform.dart' as hack; -import 'package:test_core/src/runner/reporter.dart' as hack; - -import '../browser.dart'; -import '../common.dart'; -import '../environment.dart'; -import '../exceptions.dart'; -import '../pipeline.dart'; -import '../test_platform.dart'; -import '../utils.dart'; - -/// Runs web tests. -/// -/// Assumes the artifacts from [CompileTestsStep] are available, either from -/// running it prior to this step locally, or by having the build graph copy -/// them from another bot. -class RunTestsStep implements PipelineStep { - RunTestsStep({ - required this.browserName, - required this.isDebug, - required this.doUpdateScreenshotGoldens, - required this.requireSkiaGold, - this.testFiles, - required this.overridePathToCanvasKit, - required this.isWasm - }); - - final String browserName; - final List? testFiles; - final bool isDebug; - final bool isWasm; - final bool doUpdateScreenshotGoldens; - final String? overridePathToCanvasKit; - - /// Require Skia Gold to be available and reachable. - final bool requireSkiaGold; - - @override - String get description => 'run_tests'; - - @override - bool get isSafeToInterrupt => true; - - @override - Future interrupt() async {} - - @override - Future run() async { - await _prepareTestResultsDirectory(); - - final BrowserEnvironment browserEnvironment = getBrowserEnvironment(browserName, enableWasmGC: isWasm); - await browserEnvironment.prepare(); - - final SkiaGoldClient? skiaClient = await _createSkiaClient(); - final List testFiles = this.testFiles ?? findAllTests(); - - final TestsByRenderer sortedTests = sortTestsByRenderer(testFiles, isWasm); - - bool testsPassed = true; - - if (sortedTests.htmlTests.isNotEmpty) { - await _runTestBatch( - testFiles: sortedTests.htmlTests, - renderer: Renderer.html, - browserEnvironment: browserEnvironment, - expectFailure: false, - isDebug: isDebug, - isWasm: isWasm, - doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, - skiaClient: skiaClient, - overridePathToCanvasKit: overridePathToCanvasKit, - ); - testsPassed &= io.exitCode == 0; - } - - if (sortedTests.canvasKitTests.isNotEmpty) { - await _runTestBatch( - testFiles: sortedTests.canvasKitTests, - renderer: Renderer.canvasKit, - browserEnvironment: browserEnvironment, - expectFailure: false, - isDebug: isDebug, - isWasm: isWasm, - doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, - skiaClient: skiaClient, - overridePathToCanvasKit: overridePathToCanvasKit, - ); - testsPassed &= io.exitCode == 0; - } - - // TODO(jacksongardner): enable this test suite on safari - // For some reason, Safari is flaky when running the Skwasm test suite - // See https://github.com/flutter/flutter/issues/115312 - if (browserName != kSafari && sortedTests.skwasmTests.isNotEmpty) { - await _runTestBatch( - testFiles: sortedTests.skwasmTests, - renderer: Renderer.skwasm, - browserEnvironment: browserEnvironment, - expectFailure: false, - isDebug: isDebug, - isWasm: isWasm, - doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, - skiaClient: skiaClient, - overridePathToCanvasKit: overridePathToCanvasKit, - ); - testsPassed &= io.exitCode == 0; - } - - await browserEnvironment.cleanup(); - - if (!testsPassed) { - throw ToolExit('Some tests failed'); - } - } - - Future _createSkiaClient() async { - final SkiaGoldClient skiaClient = SkiaGoldClient( - environment.webUiSkiaGoldDirectory, - dimensions: { - 'Browser': browserName, - if (isWasm) 'Wasm': 'true', - }, - ); - - if (await _checkSkiaClient(skiaClient)) { - return skiaClient; - } - - if (requireSkiaGold) { - throw ToolExit('Skia Gold is required but is unavailable.'); - } - - return null; - } - - /// Checks whether the Skia Client is usable in this environment. - Future _checkSkiaClient(SkiaGoldClient skiaClient) async { - // Now let's check whether Skia Gold is reachable or not. - if (isLuci) { - if (isSkiaGoldClientAvailable) { - try { - await skiaClient.auth(); - return true; - } catch (e) { - print(e); - } - } - } else { - try { - // Check if we can reach Gold. - await skiaClient.getExpectationForTest(''); - return true; - } on io.OSError catch (_) { - print('OSError occurred, could not reach Gold.'); - } on io.SocketException catch (_) { - print('SocketException occurred, could not reach Gold.'); - } - } - - return false; - } -} - -Future _prepareTestResultsDirectory() async { - if (environment.webUiTestResultsDirectory.existsSync()) { - environment.webUiTestResultsDirectory.deleteSync(recursive: true); - } - environment.webUiTestResultsDirectory.createSync(recursive: true); -} - -/// Runs a batch of tests. -/// -/// Unless [expectFailure] is set to false, sets [io.exitCode] to a non-zero -/// value if any tests fail. -Future _runTestBatch({ - required List testFiles, - required Renderer renderer, - required bool isDebug, - required bool isWasm, - required BrowserEnvironment browserEnvironment, - required bool doUpdateScreenshotGoldens, - required bool expectFailure, - required SkiaGoldClient? skiaClient, - required String? overridePathToCanvasKit, -}) async { - final String configurationFilePath = pathlib.join( - environment.webUiRootDir.path, - browserEnvironment.packageTestConfigurationYamlFile, - ); - final String precompiledBuildDir = pathlib.join( - environment.webUiBuildDir.path, - getBuildDirForRenderer(renderer), - ); - final List testArgs = [ - ...['-r', 'compact'], - // Disable concurrency. Running with concurrency proved to be flaky. - '--concurrency=1', - if (isDebug) '--pause-after-load', - // Don't pollute logs with output from tests that are expected to fail. - if (expectFailure) - '--reporter=name-only', - '--platform=${browserEnvironment.packageTestRuntime.identifier}', - '--precompiled=$precompiledBuildDir', - '--configuration=$configurationFilePath', - '--', - ...testFiles.map((FilePath f) => f.relativeToWebUi), - ]; - - if (expectFailure) { - hack.registerReporter( - 'name-only', - hack.ReporterDetails( - 'Prints the name of the test, but suppresses all other test output.', - (_, hack.Engine engine, __) => NameOnlyReporter(engine)), - ); - } - - hack.registerPlatformPlugin([ - browserEnvironment.packageTestRuntime, - ], () { - return BrowserPlatform.start( - browserEnvironment: browserEnvironment, - renderer: renderer, - // It doesn't make sense to update a screenshot for a test that is - // expected to fail. - doUpdateScreenshotGoldens: !expectFailure && doUpdateScreenshotGoldens, - skiaClient: skiaClient, - overridePathToCanvasKit: overridePathToCanvasKit, - isWasm: isWasm, - ); - }); - - // We want to run tests with `web_ui` as a working directory. - final dynamic originalCwd = io.Directory.current; - io.Directory.current = environment.webUiRootDir.path; - try { - await test.main(testArgs); - } finally { - io.Directory.current = originalCwd; - } - - if (expectFailure) { - if (io.exitCode != 0) { - // It failed, as expected. - print('Test successfully failed, as expected.'); - io.exitCode = 0; - } else { - io.stderr.writeln( - 'Tests ${testFiles.join(', ')} did not fail. Expected failure.', - ); - io.exitCode = 1; - } - } -} - -/// Prints the name of the test, but suppresses all other test output. -/// -/// This is useful to prevent pollution of logs by tests that are expected to -/// fail. -class NameOnlyReporter implements hack.Reporter { - NameOnlyReporter(hack.Engine testEngine) { - testEngine.onTestStarted.listen(_printTestName); - } - - void _printTestName(hack.LiveTest test) { - print('Running ${test.groups.map((hack.Group group) => group.name).join(' ')} ${test.individualName}'); - } - - @override - void pause() {} - - @override - void resume() {} -} diff --git a/lib/web_ui/dev/suite_filter.dart b/lib/web_ui/dev/suite_filter.dart new file mode 100644 index 0000000000000..25b566a1ff18d --- /dev/null +++ b/lib/web_ui/dev/suite_filter.dart @@ -0,0 +1,123 @@ +// Copyright 2013 The Flutter Authors. 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' as io; + +import 'felt_config.dart'; + +class SuiteFilterResult { + SuiteFilterResult.accepted(); + SuiteFilterResult.rejected(String reason) : rejectReason = reason; + + String? rejectReason; + + bool get isAccepted => rejectReason == null; +} + +abstract class SuiteFilter { + SuiteFilterResult filterSuite(TestSuite suite); +} + +abstract class AllowListSuiteFilter implements SuiteFilter { + AllowListSuiteFilter({ required this.allowList }); + + final Set allowList; + + T getAttributeForSuite(TestSuite suite); + + String rejectReason(TestSuite suite) { + return '${getAttributeForSuite(suite)} does not match filter.'; + } + + @override + SuiteFilterResult filterSuite(TestSuite suite) { + if (allowList.contains(getAttributeForSuite(suite))) { + return SuiteFilterResult.accepted(); + } else { + return SuiteFilterResult.rejected(rejectReason(suite)); + } + } +} + +class BrowserSuiteFilter extends AllowListSuiteFilter { + BrowserSuiteFilter({required super.allowList}); + + @override + BrowserName getAttributeForSuite(TestSuite suite) => suite.runConfig.browser; +} + +class SuiteNameFilter extends AllowListSuiteFilter { + SuiteNameFilter({required super.allowList}); + + @override + String getAttributeForSuite(TestSuite suite) => suite.name; +} + +class BundleNameFilter extends AllowListSuiteFilter { + BundleNameFilter({required super.allowList}); + + @override + String getAttributeForSuite(TestSuite suite) => suite.testBundle.name; +} + +class FileFilter extends BundleNameFilter { + FileFilter({required super.allowList}); + + @override + String rejectReason(TestSuite suite) { + return "Doesn't contain any of the indicated files."; + } +} + +class CompilerFilter extends AllowListSuiteFilter { + CompilerFilter({required super.allowList}); + + @override + Compiler getAttributeForSuite(TestSuite suite) => suite.testBundle.compileConfig.compiler; +} + +class RendererFilter extends AllowListSuiteFilter { + RendererFilter({required super.allowList}); + + @override + Renderer getAttributeForSuite(TestSuite suite) => suite.testBundle.compileConfig.renderer; +} + +class CanvasKitVariantFilter extends AllowListSuiteFilter { + CanvasKitVariantFilter({required super.allowList}); + + @override + // TODO(jackson): Is this the right default? + CanvasKitVariant getAttributeForSuite(TestSuite suite) => suite.runConfig.variant ?? CanvasKitVariant.full; +} + +Set get _supportedPlatformBrowsers { + if (io.Platform.isLinux) { + return { + BrowserName.chrome, + BrowserName.firefox + }; + } else if (io.Platform.isMacOS) { + return { + BrowserName.chrome, + BrowserName.firefox, + BrowserName.safari, + }; + } else if (io.Platform.isWindows) { + return { + BrowserName.chrome, + BrowserName.edge, + }; + } else { + throw AssertionError('Unsupported OS: ${io.Platform.operatingSystem}'); + } +} + +class PlatformBrowserFilter extends BrowserSuiteFilter { + PlatformBrowserFilter() : super(allowList: _supportedPlatformBrowsers); + + @override + String rejectReason(TestSuite suite) => + 'Current platform (${io.Platform.operatingSystem}) does not support browser ${suite.runConfig.browser}'; +} diff --git a/lib/web_ui/dev/test_dart2wasm.js b/lib/web_ui/dev/test_dart2wasm.js index 884fbc81fdecc..a9c737083f6be 100644 --- a/lib/web_ui/dev/test_dart2wasm.js +++ b/lib/web_ui/dev/test_dart2wasm.js @@ -57,7 +57,7 @@ window.onload = async function () { const isSkwasm = link.hasAttribute('skwasm'); const imports = isSkwasm ? new Promise((resolve) => { const skwasmScript = document.createElement('script'); - skwasmScript.src = '/skwasm/skwasm.js'; + skwasmScript.src = '/canvaskit/skwasm.js'; document.body.appendChild(skwasmScript); skwasmScript.addEventListener('load', async () => { diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index 6f5949c5b46e0..e30d3a421d687 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -38,6 +38,7 @@ import 'package:web_test_utils/image_compare.dart'; import 'browser.dart'; import 'environment.dart' as env; +import 'felt_config.dart'; import 'utils.dart'; const Map coopCoepHeaders = { @@ -47,12 +48,11 @@ const Map coopCoepHeaders = { /// Custom test platform that serves web engine unit tests. class BrowserPlatform extends PlatformPlugin { - BrowserPlatform._({ + BrowserPlatform._(this.suite, { required this.browserEnvironment, required this.server, - required this.renderer, required this.isDebug, - required this.isWasm, + required this.isVerbose, required this.doUpdateScreenshotGoldens, required this.packageConfig, required this.skiaClient, @@ -74,8 +74,14 @@ class BrowserPlatform extends PlatformPlugin { .add(_packageUrlHandler) .add(_canvasKitOverrideHandler) - // Serves files from the out/web_tests/ directory at the root (/) URL path. - .add(buildDirectoryHandler) + // Serves files from the bundle's output build directory + .add(createSimpleDirectoryHandler(getBundleBuildDirectory(suite.testBundle))) + + // Serves files from the out/web_tests/artifacts directory at the root (/) URL path. + .add(createSimpleDirectoryHandler(env.environment.webTestsArtifactsDir)) + + // Serves files from thes test set directory + .add(createSimpleDirectoryHandler(getTestSetDirectory(suite.testBundle.testSet))) .add(_testImageListingHandler) // Serves the initial HTML for the test. @@ -112,22 +118,22 @@ class BrowserPlatform extends PlatformPlugin { /// /// If [doUpdateScreenshotGoldens] is true updates screenshot golden files /// instead of failing the test on screenshot mismatches. - static Future start({ + static Future start(TestSuite suite, { required BrowserEnvironment browserEnvironment, - required Renderer renderer, required bool doUpdateScreenshotGoldens, required SkiaGoldClient? skiaClient, required String? overridePathToCanvasKit, required bool isWasm, + required bool isVerbose, }) async { final shelf_io.IOServer server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); return BrowserPlatform._( + suite, browserEnvironment: browserEnvironment, - renderer: renderer, server: server, isDebug: Configuration.current.pauseAfterLoad, - isWasm: isWasm, + isVerbose: isVerbose, doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, packageConfig: await loadPackageConfigUri((await Isolate.packageConfig)!), skiaClient: skiaClient, @@ -135,12 +141,14 @@ class BrowserPlatform extends PlatformPlugin { ); } + final TestSuite suite; + /// If true, runs the browser with a visible windows (i.e. not headless) and /// pauses before running the tests to give the developer a chance to set /// breakpoints in the code. final bool isDebug; - final bool isWasm; + final bool isVerbose; /// The underlying server. final shelf.Server server; @@ -148,12 +156,12 @@ class BrowserPlatform extends PlatformPlugin { /// Provides the environment for the browser running tests. final BrowserEnvironment browserEnvironment; - /// The renderer that tests are running under. - final Renderer renderer; - /// The URL for this server. Uri get url => server.url.resolve('/'); + bool get isWasm => suite.testBundle.compileConfig.compiler == Compiler.dart2wasm; + bool get needsCrossOriginIsolated => isWasm && suite.testBundle.compileConfig.renderer == Renderer.skwasm; + /// A [OneOffHandler] for servicing WebSocket connections for /// [BrowserManager]s. /// @@ -230,7 +238,7 @@ class BrowserPlatform extends PlatformPlugin { } final Directory testImageDirectory = Directory(p.join( - env.environment.webUiBuildDir.path, + env.environment.webTestsArtifactsDir.path, 'test_images', )); @@ -252,7 +260,9 @@ class BrowserPlatform extends PlatformPlugin { } Future _fileNotFoundCatcher(shelf.Request request) async { - print('HTTP 404: ${request.url}'); + if (isVerbose) { + print('HTTP 404: ${request.url}'); + } return shelf.Response.notFound('File not found'); } @@ -383,10 +393,12 @@ class BrowserPlatform extends PlatformPlugin { final String filename = requestData['filename'] as String; if (!(await browserManager).supportsScreenshots) { - print( - 'Skipping screenshot check for $filename. Current browser/OS ' - 'combination does not support screenshots.', - ); + if (isVerbose) { + print( + 'Skipping screenshot check for $filename. Current browser/OS ' + 'combination does not support screenshots.', + ); + } return shelf.Response.ok(json.encode('OK')); } @@ -419,6 +431,7 @@ class BrowserPlatform extends PlatformPlugin { filename, skiaClient, isCanvaskitTest: isCanvaskitTest, + verbose: isVerbose, ); } @@ -443,52 +456,55 @@ class BrowserPlatform extends PlatformPlugin { '.woff2': 'font/woff2', }; - /// A simple file handler that serves files whose URLs and paths are + /// Creates a simple file handler that serves files whose URLs and paths are /// statically known. /// /// This is used for trivial use-cases, such as `favicon.ico`, host pages, etc. - shelf.Response buildDirectoryHandler(shelf.Request request) { - File fileInBuild = File(p.join( - env.environment.webUiBuildDir.path, - getBuildDirForRenderer(renderer), - request.url.path, - )); - - // If we can't find the file in the renderer-specific `build` subdirectory, - // then it may be in the top-level `build` subdirectory. - if (!fileInBuild.existsSync()) { - fileInBuild = File(p.join( - env.environment.webUiBuildDir.path, + shelf.Handler createSimpleDirectoryHandler(Directory directory) { + return (shelf.Request request) { + final File fileInDirectory = File(p.join( + directory.path, request.url.path, )); - } - if (!fileInBuild.existsSync()) { - return shelf.Response.notFound('File not found: ${request.url.path}'); - } + if (!fileInDirectory.existsSync()) { + return shelf.Response.notFound('File not found: ${request.url.path}'); + } - final String extension = p.extension(fileInBuild.path); - final String? contentType = contentTypes[extension]; + final String extension = p.extension(fileInDirectory.path); + final String? contentType = contentTypes[extension]; - if (contentType == null) { - final String error = - 'Failed to determine Content-Type for "${request.url.path}".'; - stderr.writeln(error); - return shelf.Response.internalServerError(body: error); - } + if (contentType == null) { + final String error = + 'Failed to determine Content-Type for "${request.url.path}".'; + stderr.writeln(error); + return shelf.Response.internalServerError(body: error); + } - final bool needsCoopCoep = - extension == '.js' || - extension == '.mjs' || - extension == '.html'; - return shelf.Response.ok( - fileInBuild.readAsBytesSync(), - headers: { - HttpHeaders.contentTypeHeader: contentType, - if (needsCoopCoep && isWasm && renderer == Renderer.skwasm) - ...coopCoepHeaders, - }, - ); + final bool isScript = + extension == '.js' || + extension == '.mjs' || + extension == '.html'; + return shelf.Response.ok( + fileInDirectory.readAsBytesSync(), + headers: { + HttpHeaders.contentTypeHeader: contentType, + if (isScript && needsCrossOriginIsolated) + ...coopCoepHeaders, + }, + ); + }; + } + + String getCanvasKitVariant() { + switch (suite.runConfig.variant) { + case CanvasKitVariant.full: + return 'full'; + case CanvasKitVariant.chromium: + return 'chromium'; + case null: + return 'auto'; + } } /// Serves the HTML file that bootstraps the test. @@ -498,12 +514,14 @@ class BrowserPlatform extends PlatformPlugin { if (path.endsWith('.html')) { final String test = '${p.withoutExtension(path)}.dart'; + final bool linkSkwasm = suite.testBundle.compileConfig.renderer == Renderer.skwasm; // Link to the Dart wrapper. final String scriptBase = htmlEscape.convert(p.basename(test)); - final String link = ''; + final String link = ''; final String testRunner = isWasm ? '/test_dart2wasm.js' : 'packages/test/dart.js'; + return shelf.Response.ok(''' @@ -512,7 +530,8 @@ class BrowserPlatform extends PlatformPlugin { $link @@ -521,7 +540,7 @@ class BrowserPlatform extends PlatformPlugin { ''', headers: { 'Content-Type': 'text/html', - if (isWasm && renderer == Renderer.skwasm) + if (needsCrossOriginIsolated) ...coopCoepHeaders }); } @@ -554,8 +573,7 @@ class BrowserPlatform extends PlatformPlugin { } _checkNotClosed(); - final Uri suiteUrl = url.resolveUri(p.toUri('${p.withoutExtension( - p.relative(path, from: env.environment.webUiRootDir.path))}.html')); + final Uri suiteUrl = url.resolveUri(p.toUri('${p.withoutExtension(path)}.html')); _checkNotClosed(); final BrowserManager? browserManager = await _startBrowserManager(); @@ -565,10 +583,10 @@ class BrowserPlatform extends PlatformPlugin { } _checkNotClosed(); - final RunnerSuite suite = + final RunnerSuite runnerSuite = await browserManager.load(path, suiteUrl, suiteConfig, message); _checkNotClosed(); - return suite; + return runnerSuite; } Future? _browserManager; @@ -598,9 +616,8 @@ class BrowserPlatform extends PlatformPlugin { url: hostUrl, future: completer.future, packageConfig: packageConfig, - isWasm: isWasm, debug: isDebug, - renderer: renderer, + sourceMapDirectory: isWasm ? null : getBundleBuildDirectory(suite.testBundle), ); // Store null values for browsers that error out so we know not to load them @@ -696,7 +713,7 @@ class BrowserManager { /// Creates a new BrowserManager that communicates with the browser over /// [webSocket]. BrowserManager._(this.packageConfig, this._browser, this._browserEnvironment, - this._renderer, this._isWasm, WebSocketChannel webSocket) { + this._sourceMapDirectory, 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. @@ -742,8 +759,8 @@ class BrowserManager { /// The browser environment for this test. final BrowserEnvironment _browserEnvironment; - /// The renderer for this test. - final Renderer _renderer; + /// The directory containing sourcemaps for test files + final Directory? _sourceMapDirectory; /// The channel used to communicate with the browser. /// @@ -768,9 +785,6 @@ class BrowserManager { /// Whether the channel to the browser has closed. bool _closed = false; - /// Whether we are running tests that have been compiled to WebAssembly. - final bool _isWasm; - /// The completer for [_BrowserEnvironment.displayPause]. /// /// This will be `null` as long as the browser isn't displaying a pause @@ -812,8 +826,7 @@ class BrowserManager { required Uri url, required Future future, required PackageConfig packageConfig, - required Renderer renderer, - required bool isWasm, + Directory? sourceMapDirectory, bool debug = false, }) async { final Browser browser = @@ -824,8 +837,7 @@ class BrowserManager { future: future, packageConfig: packageConfig, browser: browser, - renderer: renderer, - isWasm: isWasm, + sourceMapDirectory: sourceMapDirectory, debug: debug); } @@ -835,8 +847,7 @@ class BrowserManager { required Future future, required PackageConfig packageConfig, required Browser browser, - required Renderer renderer, - required bool isWasm, + Directory? sourceMapDirectory, bool debug = false, }) { final Completer completer = Completer(); @@ -858,7 +869,7 @@ class BrowserManager { return; } completer.complete(BrowserManager._( - packageConfig, browser, browserEnvironment, renderer, isWasm, webSocket)); + packageConfig, browser, browserEnvironment, sourceMapDirectory, webSocket)); }).catchError((Object error, StackTrace stackTrace) { browser.close(); if (completer.isCompleted) { @@ -942,7 +953,7 @@ class BrowserManager { suiteChannel, message); - if (_isWasm) { + if (_sourceMapDirectory == null) { // We don't have mapping for wasm yet. But we should send a message // to let the host page move forward. controller!.channel('test.browser.mapper').sink.add(null); @@ -951,8 +962,11 @@ class BrowserManager { '${p.basename(path)}.browser_test.dart.js.map'; final String pathToTest = p.dirname(path); - final String mapPath = p.join(env.environment.webUiBuildDir.path, - getBuildDirForRenderer(_renderer), pathToTest, sourceMapFileName); + final String mapPath = p.join( + _sourceMapDirectory!.path, + pathToTest, + sourceMapFileName + ); final Map packageMap = { for (Package p in packageConfig.packages) p.name: p.packageUriRoot diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index f3756a82ba480..83d9da6c3f8b6 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -10,9 +10,15 @@ import 'package:path/path.dart' as path; import 'package:watcher/src/watch_event.dart'; +import 'environment.dart'; +import 'exceptions.dart'; +import 'felt_config.dart'; +import 'generate_builder_json.dart'; import 'pipeline.dart'; -import 'steps/compile_tests_step.dart'; -import 'steps/run_tests_step.dart'; +import 'steps/compile_bundle_step.dart'; +import 'steps/copy_artifacts_step.dart'; +import 'steps/run_suite_step.dart'; +import 'suite_filter.dart'; import 'utils.dart'; /// Runs tests. @@ -25,6 +31,11 @@ class TestCommand extends Command with ArgUtils { 'opportunity to add breakpoints or inspect loaded code before ' 'running the code.', ) + ..addFlag( + 'verbose', + abbr: 'v', + help: 'Enable verbose output.' + ) ..addFlag( 'watch', abbr: 'w', @@ -32,16 +43,43 @@ class TestCommand extends Command with ArgUtils { 'made.', ) ..addFlag( - 'use-system-flutter', - help: 'integration tests are using flutter repository for various tasks' - ', such as flutter drive, flutter pub get. If this flag is set, felt ' - 'will use flutter command without cloning the repository. This flag ' - 'can save internet bandwidth. However use with caution. Note that ' - 'since flutter repo is always synced to youngest commit older than ' - 'the engine commit for the tests running in CI, the tests results ' - "won't be consistent with CIs when this flag is set. flutter " - 'command should be set in the PATH for this flag to be useful.' - 'This flag can also be used to test local Flutter changes.') + 'list', + help: + 'Lists the bundles that would be compiled and the suites that ' + 'will be run as part of this invocation, without actually ' + 'compiling or running them.' + ) + ..addFlag( + 'generate-builder-json', + help: + 'Generates JSON for the engine_v2 builders to build and copy all' + 'artifacts, compile all test bundles, and run all test suites on' + 'all platforms.' + ) + ..addFlag( + 'compile', + help: + 'Compile test bundles. If this is specified on its own, we will ' + 'only compile and not run the suites.' + ) + ..addFlag( + 'run', + help: + 'Run test suites. If this is specified on its own, we will only ' + 'run the suites and not compile the bundles.' + ) + ..addFlag( + 'copy-artifacts', + help: + 'Copy artifacts needed for test suites. If this is specified on ' + 'its own, we will only copy the artifacts and not compile or run' + 'the tests bundles or suites.' + ) + ..addFlag( + 'profile', + help: + 'Use artifacts from the profile build instead of release.' + ) ..addFlag( 'require-skia-gold', help: @@ -55,11 +93,29 @@ class TestCommand extends Command with ArgUtils { '.dart_tool/goldens. Use this option to bulk-update all screenshots, ' 'for example, when a new browser version affects pixels.', ) - ..addOption( + ..addMultiOption( 'browser', - defaultsTo: 'chrome', - help: 'An option to choose a browser to run the tests. By default ' - 'tests run in Chrome.', + help: 'Filter test suites by browser.', + ) + ..addMultiOption( + 'compiler', + help: 'Filter test suites by compiler.', + ) + ..addMultiOption( + 'renderer', + help: 'Filter test suites by renderer.', + ) + ..addMultiOption( + 'canvaskit-variant', + help: 'Filter test suites by CanvasKit variant.', + ) + ..addMultiOption( + 'suite', + help: 'Filter test suites by suite name.', + ) + ..addMultiOption( + 'bundle', + help: 'Filter test suites by bundle name.', ) ..addFlag( 'fail-early', @@ -77,11 +133,6 @@ class TestCommand extends Command with ArgUtils { ..addFlag( 'wasm', help: 'Whether the test we are running are compiled to webassembly.' - ) - ..addFlag( - 'use-local-canvaskit', - help: 'Optional. Whether or not to use the locally built version of ' - 'CanvasKit in the tests.', ); } @@ -93,9 +144,9 @@ class TestCommand extends Command with ArgUtils { bool get isWatchMode => boolArg('watch'); - bool get failEarly => boolArg('fail-early'); + bool get isList => boolArg('list'); - bool get isWasm => boolArg('wasm'); + bool get failEarly => boolArg('fail-early'); /// Whether to start the browser in debug mode. /// @@ -103,17 +154,10 @@ class TestCommand extends Command with ArgUtils { /// you set breakpoints or inspect the code. bool get isDebug => boolArg('debug'); - /// Paths to targets to run, e.g. a single test. - List get targets => argResults!.rest; + bool get isVerbose => boolArg('verbose'); /// The target test files to run. - List get targetFiles => targets.map((String t) => FilePath.fromCwd(t)).toList(); - - /// Whether all tests should run. - bool get runAllTests => targets.isEmpty; - - /// The name of the browser to run tests in. - String get browserName => stringArg('browser'); + List get targetFiles => argResults!.rest.map((String t) => FilePath.fromCwd(t)).toList(); /// When running screenshot tests, require Skia Gold to be available and /// reachable. @@ -126,31 +170,219 @@ class TestCommand extends Command with ArgUtils { /// Path to a CanvasKit build. Overrides the default CanvasKit. String? get overridePathToCanvasKit => argResults!['canvaskit-path'] as String?; - /// Whether or not to use the locally built version of CanvasKit. - bool get useLocalCanvasKit => boolArg('use-local-canvaskit'); + final FeltConfig config = FeltConfig.fromFile( + path.join(environment.webUiTestDir.path, 'felt_config.yaml') + ); + + BrowserSuiteFilter? makeBrowserFilter() { + final List? browserArgs = argResults!['browser'] as List?; + if (browserArgs == null || browserArgs.isEmpty) { + return null; + } + final Set browserNames = Set.from(browserArgs.map((String arg) => BrowserName.values.byName(arg))); + return BrowserSuiteFilter(allowList: browserNames); + } + + CompilerFilter? makeCompilerFilter() { + final List? compilerArgs = argResults!['compiler'] as List?; + if (compilerArgs == null || compilerArgs.isEmpty) { + return null; + } + final Set compilers = Set.from(compilerArgs.map((String arg) => Compiler.values.byName(arg))); + return CompilerFilter(allowList: compilers); + } + + RendererFilter? makeRendererFilter() { + final List? rendererArgs = argResults!['renderer'] as List?; + if (rendererArgs == null || rendererArgs.isEmpty) { + return null; + } + final Set renderers = Set.from(rendererArgs.map((String arg) => Renderer.values.byName(arg))); + return RendererFilter(allowList: renderers); + } + + CanvasKitVariantFilter? makeCanvasKitVariantFilter() { + final List? variantArgs = argResults!['canvaskit-variant'] as List?; + if (variantArgs == null || variantArgs.isEmpty) { + return null; + } + final Set variants = Set.from(variantArgs.map((String arg) => CanvasKitVariant.values.byName(arg))); + return CanvasKitVariantFilter(allowList: variants); + } + + SuiteNameFilter? makeSuiteNameFilter() { + final List? suiteNameArgs = argResults!['suite'] as List?; + if (suiteNameArgs == null || suiteNameArgs.isEmpty) { + return null; + } + + final Iterable allSuiteNames = config.testSuites.map((TestSuite suite) => suite.name); + for (final String suiteName in suiteNameArgs) { + if (!allSuiteNames.contains(suiteName)) { + throw ToolExit('No suite found named $suiteName'); + } + } + return SuiteNameFilter(allowList: Set.from(suiteNameArgs)); + } + + BundleNameFilter? makeBundleNameFilter() { + final List? bundleNameArgs = argResults!['bundle'] as List?; + if (bundleNameArgs == null || bundleNameArgs.isEmpty) { + return null; + } + + final Iterable allBundleNames = config.testSuites.map( + (TestSuite suite) => suite.testBundle.name + ); + for (final String bundleName in bundleNameArgs) { + if (!allBundleNames.contains(bundleName)) { + throw ToolExit('No bundle found named $bundleName'); + } + } + return BundleNameFilter(allowList: Set.from(bundleNameArgs)); + } + + FileFilter? makeFileFilter() { + final List tests = targetFiles; + if (tests.isEmpty) { + return null; + } + final Set bundleNames = {}; + for (final FilePath testPath in tests) { + if (!io.File(testPath.absolute).existsSync()) { + throw ToolExit('Test path not found: $testPath'); + } + bool bundleFound = false; + for (final TestBundle bundle in config.testBundles) { + final String testSetPath = getTestSetDirectory(bundle.testSet).path; + if (path.isWithin(testSetPath, testPath.absolute)) { + bundleFound = true; + bundleNames.add(bundle.name); + } + } + if (!bundleFound) { + throw ToolExit('Test path not in any known test bundle: $testPath'); + } + } + return FileFilter(allowList: bundleNames); + } + + List get suiteFilters { + final BrowserSuiteFilter? browserFilter = makeBrowserFilter(); + final CompilerFilter? compilerFilter = makeCompilerFilter(); + final RendererFilter? rendererFilter = makeRendererFilter(); + final CanvasKitVariantFilter? canvaskitVariantFilter = makeCanvasKitVariantFilter(); + final SuiteNameFilter? suiteNameFilter = makeSuiteNameFilter(); + final BundleNameFilter? bundleNameFilter = makeBundleNameFilter(); + final FileFilter? fileFilter = makeFileFilter(); + return [ + PlatformBrowserFilter(), + if (browserFilter != null) browserFilter, + if (compilerFilter != null) compilerFilter, + if (rendererFilter != null) rendererFilter, + if (canvaskitVariantFilter != null) canvaskitVariantFilter, + if (suiteNameFilter != null) suiteNameFilter, + if (bundleNameFilter != null) bundleNameFilter, + if (fileFilter != null) fileFilter, + ]; + } + + List _filterTestSuites() { + if (isVerbose) { + print('Filtering suites...'); + } + final List filters = suiteFilters; + final List filteredSuites = config.testSuites.where((TestSuite suite) { + for (final SuiteFilter filter in filters) { + final SuiteFilterResult result = filter.filterSuite(suite); + if (!result.isAccepted) { + if (isVerbose) { + print(' ${suite.name.ansiCyan} rejected for reason: ${result.rejectReason}'); + } + return false; + } + } + return true; + }).toList(); + return filteredSuites; + } + + List _filterBundlesForSuites(List suites) { + final Set seenBundles = + Set.from(suites.map((TestSuite suite) => suite.testBundle)); + return config.testBundles.where((TestBundle bundle) => seenBundles.contains(bundle)).toList(); + } + + ArtifactDependencies _artifactsForSuites(List suites) { + return suites.fold(ArtifactDependencies.none(), + (ArtifactDependencies deps, TestSuite suite) => deps | suite.artifactDependencies); + } @override Future run() async { - final List testFiles = runAllTests - ? findAllTests() - : targetFiles; + final List filteredSuites = _filterTestSuites(); + final List bundles = _filterBundlesForSuites(filteredSuites); + final ArtifactDependencies artifacts = _artifactsForSuites(filteredSuites); + if (boolArg('generate-builder-json')) { + print(generateBuilderJson(config)); + return true; + } + if (isList || isVerbose) { + print('Suites:'); + for (final TestSuite suite in filteredSuites) { + print(' ${suite.name.ansiCyan}'); + } + print('Bundles:'); + for (final TestBundle bundle in bundles) { + print(' ${bundle.name.ansiMagenta}'); + } + print('Artifacts:'); + if (artifacts.canvasKit) { + print(' canvaskit'.ansiYellow); + } + if (artifacts.canvasKitChromium) { + print(' canvaskit_chromium'.ansiYellow); + } + if (artifacts.skwasm) { + print(' skwasm'.ansiYellow); + } + } + if (isList) { + return true; + } + + bool shouldRun = boolArg('run'); + bool shouldCompile = boolArg('compile'); + bool shouldCopyArtifacts = boolArg('copy-artifacts'); + if (!shouldRun && !shouldCompile && !shouldCopyArtifacts) { + // If none of these is specified, we should assume we need to do all of them. + shouldRun = true; + shouldCompile = true; + shouldCopyArtifacts = true; + } + final Set? testFiles = targetFiles.isEmpty ? null : Set.from(targetFiles); final Pipeline testPipeline = Pipeline(steps: [ if (isWatchMode) ClearTerminalScreenStep(), - CompileTestsStep( - testFiles: testFiles, - useLocalCanvasKit: useLocalCanvasKit, - isWasm: isWasm - ), - RunTestsStep( - browserName: browserName, - testFiles: testFiles, - isDebug: isDebug, - isWasm: isWasm, - doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, - requireSkiaGold: requireSkiaGold, - overridePathToCanvasKit: overridePathToCanvasKit, - ), + if (shouldCopyArtifacts) CopyArtifactsStep(artifacts, isProfile: boolArg('profile')), + if (shouldCompile) + for (final TestBundle bundle in bundles) + CompileBundleStep( + bundle: bundle, + isVerbose: isVerbose, + testFiles: testFiles, + ), + if (shouldRun) + for (final TestSuite suite in filteredSuites) + RunSuiteStep( + suite, + isDebug: isDebug, + isVerbose: isVerbose, + doUpdateScreenshotGoldens: doUpdateScreenshotGoldens, + requireSkiaGold: requireSkiaGold, + overridePathToCanvasKit: overridePathToCanvasKit, + testFiles: testFiles, + ), ]); try { diff --git a/lib/web_ui/dev/utils.dart b/lib/web_ui/dev/utils.dart index 2fe7244c20d98..2907dffd3a859 100644 --- a/lib/web_ui/dev/utils.dart +++ b/lib/web_ui/dev/utils.dart @@ -12,12 +12,15 @@ import 'package:path/path.dart' as path; import 'environment.dart'; import 'exceptions.dart'; +import 'felt_config.dart'; class FilePath { FilePath.fromCwd(String relativePath) : _absolutePath = path.absolute(relativePath); FilePath.fromWebUi(String relativePath) : _absolutePath = path.join(environment.webUiRootDir.path, relativePath); + FilePath.fromTestSet(TestSet testSet, String relativePath) + : _absolutePath = path.join(getTestSetDirectory(testSet).path, relativePath); final String _absolutePath; @@ -368,82 +371,43 @@ Future cleanup() async { } } -/// Scans the test/ directory for test files and returns them. -List findAllTests() { - return environment.webUiTestDir - .listSync(recursive: true) - .whereType() - .where((io.File f) => f.path.endsWith('_test.dart')) - .map((io.File f) => FilePath.fromWebUi( - path.relative(f.path, from: environment.webUiRootDir.path))) - .toList(); +io.Directory getTestSetDirectory(TestSet testSet) { + return io.Directory( + path.join( + environment.webUiTestDir.path, + testSet.directory, + ) + ); } -/// The renderer used to run the test. -enum Renderer { - html, - canvasKit, - skwasm, +io.Directory getBundleBuildDirectory(TestBundle bundle) { + return io.Directory( + path.join( + environment.webUiBuildDir.path, + 'test_bundles', + bundle.name, + ) + ); } -/// The `FilePath`s for all the tests, organized by renderer. -class TestsByRenderer { - TestsByRenderer(this.htmlTests, this.canvasKitTests, this.skwasmTests); - - /// Tests which should be run with the HTML renderer. - final List htmlTests; +extension AnsiColors on String { + static bool shouldEscape = io.stdout.hasTerminal && io.stdout.supportsAnsiEscapes; - /// Tests which should be run with the CanvasKit renderer. - final List canvasKitTests; + static const String _noColorCode = '\u001b[39m'; - /// Tests which should be run with the Skwasm renderer. - final List skwasmTests; + String _wrapText(String prefix, String suffix) => shouldEscape + ? '$prefix$this$suffix' : this; - /// The total number of targets to compile. - /// - /// The number of uiTests is doubled since they are compiled twice: once for - /// the HTML renderer and once for the CanvasKit renderer. - int get numTargetsToCompile => htmlTests.length + canvasKitTests.length + skwasmTests.length; -} + String _colorText(String colorCode) => _wrapText(colorCode, _noColorCode); -/// Given a list of test files, organizes them by which renderer should run them. -TestsByRenderer sortTestsByRenderer(List testFiles, bool forWasm) { - final List htmlTargets = []; - final List canvasKitTargets = []; - final List skwasmTargets = []; - final String canvasKitTestDirectory = - path.join(environment.webUiTestDir.path, 'canvaskit'); - final String skwasmTestDirectory = - path.join(environment.webUiTestDir.path, 'skwasm'); - final String uiTestDirectory = - path.join(environment.webUiTestDir.path, 'ui'); - for (final FilePath testFile in testFiles) { - if (path.isWithin(canvasKitTestDirectory, testFile.absolute)) { - canvasKitTargets.add(testFile); - } else if (path.isWithin(skwasmTestDirectory, testFile.absolute)) { - skwasmTargets.add(testFile); - } else if (path.isWithin(uiTestDirectory, testFile.absolute)) { - htmlTargets.add(testFile); - canvasKitTargets.add(testFile); - if (forWasm) { - // Only add these tests in wasm mode, since JS mode has a stub renderer. - skwasmTargets.add(testFile); - } - } else { - htmlTargets.add(testFile); - } - } - return TestsByRenderer(htmlTargets, canvasKitTargets, skwasmTargets); -} + String get ansiBlack => _colorText('\u001b[30m'); + String get ansiRed => _colorText('\u001b[31m'); + String get ansiGreen => _colorText('\u001b[32m'); + String get ansiYellow => _colorText('\u001b[33m'); + String get ansiBlue => _colorText('\u001b[34m'); + String get ansiMagenta => _colorText('\u001b[35m'); + String get ansiCyan => _colorText('\u001b[36m'); + String get ansiWhite => _colorText('\u001b[37m'); -/// The build directory to compile a test into given the renderer. -String getBuildDirForRenderer(Renderer renderer) { - switch (renderer) { - case Renderer.html: - return 'html_tests'; - case Renderer.canvasKit: - return 'canvaskit_tests'; - case Renderer.skwasm: - return 'skwasm_tests'; - } + String get ansiBold => _wrapText('\u001b[1m', '\u001b[0m'); } diff --git a/lib/web_ui/test/README.md b/lib/web_ui/test/README.md new file mode 100644 index 0000000000000..9c3ae657a1411 --- /dev/null +++ b/lib/web_ui/test/README.md @@ -0,0 +1,50 @@ +............................................................................... +# Flutter Web Engine Test Suites +The flutter engine unit tests can be run with a number of different +configuration options that affect both compile time and run time. The +permutations of these options are specified in the `felt_config.yaml` file that +is colocated with this README. Here is an overview of the way the test suite +configurations are structured: + +## `compile-configs` +Specifies how the tests should be compiled. Each compile config specifies the +following: + * `name` - The name of the compile configuration. + * `compiler` - What compiler is used to compile the tests. Currently we support + `dart2js` and `dart2wasm` as values. + * `renderer` - Which renderer to use when compiling the tests. Currently we + support `html`, `canvaskit`, and `skwasm`. + +## `test-sets` +A group of files that contain unit tests. Each test set specifies the following: + * `name` - The name of the test set. + * `directory` - The name of the directory under `flutter/lib/web_ui/test` that + contains all the test files. + +## `test-bundles` +Specifies a group of tests and a compile configuration of those tests. The output +of the test bundles appears in `flutter/lib/web_ui/build/test_bundles/` +where `` is replaced by the name of the bundle. Each test bundle may be used +by multiple test suites. Each test bundle specifies the following: + * `name` - The name of the test bundle. + * `test-set` - The name of the test set that contains the tests to be compiled. + * `compile-config` - The name of the compile configuration to use. + +## `run-configs` +Specifies the test environment that should be provided to a unit test. Each run +config specifies the following: + * `name` - Name of the run configuration. + * `browser` - The browser with which to run the tests. Valid values for this are + `chrome`, `firefox`, `safari` or `edge`. + * `canvaskit-variant` - An optionally supplied argument that forces the tests to + use a particular variant of CanvasKit, either `full` or `chromium`. If none + is specified, the engine will select the variant based on its normal selection + logic. + +## `test-suites` +This is a fully specified run of a group of unit tests. They specify the following: + * `name` - Name of the test suite. + * `test-bundle` - Which compiled test bundle to use when running the suite. + * `run-config` - Which run configuration to use when runnin the tests. + * `artifact-deps` - Which gn/ninja build artifacts are needed to run the suite. + Valid values are `canvaskit`, `canvaskit_chromium` or `skwasm`. \ No newline at end of file diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 647d0627d891b..fe0fc8f4f55b9 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -13,7 +13,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; -import '../matchers.dart'; +import '../common/matchers.dart'; import 'common.dart'; import 'test_data.dart'; diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart index 05dee0bfa6962..d0a2a54bd7408 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_tt_on_test.dart @@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import '../matchers.dart'; +import '../common/matchers.dart'; import 'canvaskit_api_test.dart'; final bool isBlink = browserEngine == BrowserEngine.blink; diff --git a/lib/web_ui/test/canvaskit/frame_timings_test.dart b/lib/web_ui/test/canvaskit/frame_timings_test.dart index 7cab6bc8f0247..a22189b527946 100644 --- a/lib/web_ui/test/canvaskit/frame_timings_test.dart +++ b/lib/web_ui/test/canvaskit/frame_timings_test.dart @@ -5,7 +5,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import '../frame_timings_common.dart'; +import '../common/frame_timings_common.dart'; import 'common.dart'; void main() { diff --git a/lib/web_ui/test/canvaskit/image_golden_test.dart b/lib/web_ui/test/canvaskit/image_golden_test.dart index 6a9dfde29e8e3..3fae53fc9d978 100644 --- a/lib/web_ui/test/canvaskit/image_golden_test.dart +++ b/lib/web_ui/test/canvaskit/image_golden_test.dart @@ -11,7 +11,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; -import '../matchers.dart'; +import '../common/matchers.dart'; import 'common.dart'; import 'test_data.dart'; diff --git a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart index f7bc814c70e74..9e7def11ca9ba 100644 --- a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart +++ b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart @@ -12,8 +12,8 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart'; -import '../matchers.dart'; -import '../spy.dart'; +import '../common/matchers.dart'; +import '../common/spy.dart'; import 'common.dart'; void main() { diff --git a/lib/web_ui/test/frame_timings_common.dart b/lib/web_ui/test/common/frame_timings_common.dart similarity index 100% rename from lib/web_ui/test/frame_timings_common.dart rename to lib/web_ui/test/common/frame_timings_common.dart diff --git a/lib/web_ui/test/keyboard_test_common.dart b/lib/web_ui/test/common/keyboard_test_common.dart similarity index 100% rename from lib/web_ui/test/keyboard_test_common.dart rename to lib/web_ui/test/common/keyboard_test_common.dart diff --git a/lib/web_ui/test/matchers.dart b/lib/web_ui/test/common/matchers.dart similarity index 100% rename from lib/web_ui/test/matchers.dart rename to lib/web_ui/test/common/matchers.dart diff --git a/lib/web_ui/test/mock_engine_canvas.dart b/lib/web_ui/test/common/mock_engine_canvas.dart similarity index 100% rename from lib/web_ui/test/mock_engine_canvas.dart rename to lib/web_ui/test/common/mock_engine_canvas.dart diff --git a/lib/web_ui/test/spy.dart b/lib/web_ui/test/common/spy.dart similarity index 100% rename from lib/web_ui/test/spy.dart rename to lib/web_ui/test/common/spy.dart diff --git a/lib/web_ui/test/alarm_clock_test.dart b/lib/web_ui/test/engine/alarm_clock_test.dart similarity index 100% rename from lib/web_ui/test/alarm_clock_test.dart rename to lib/web_ui/test/engine/alarm_clock_test.dart diff --git a/lib/web_ui/test/browser_detect_test.dart b/lib/web_ui/test/engine/browser_detect_test.dart similarity index 100% rename from lib/web_ui/test/browser_detect_test.dart rename to lib/web_ui/test/engine/browser_detect_test.dart diff --git a/lib/web_ui/test/canvas_test.dart b/lib/web_ui/test/engine/canvas_test.dart similarity index 98% rename from lib/web_ui/test/canvas_test.dart rename to lib/web_ui/test/engine/canvas_test.dart index 432f0741c5d2e..324dae7c1c215 100644 --- a/lib/web_ui/test/canvas_test.dart +++ b/lib/web_ui/test/engine/canvas_test.dart @@ -8,7 +8,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import 'mock_engine_canvas.dart'; +import '../common/mock_engine_canvas.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/channel_buffers_test.dart b/lib/web_ui/test/engine/channel_buffers_test.dart similarity index 100% rename from lib/web_ui/test/channel_buffers_test.dart rename to lib/web_ui/test/engine/channel_buffers_test.dart diff --git a/lib/web_ui/test/clipboard_test.dart b/lib/web_ui/test/engine/clipboard_test.dart similarity index 100% rename from lib/web_ui/test/clipboard_test.dart rename to lib/web_ui/test/engine/clipboard_test.dart diff --git a/lib/web_ui/test/composition_test.dart b/lib/web_ui/test/engine/composition_test.dart similarity index 100% rename from lib/web_ui/test/composition_test.dart rename to lib/web_ui/test/engine/composition_test.dart diff --git a/lib/web_ui/test/engine/configuration_test.dart b/lib/web_ui/test/engine/configuration_test.dart index 33aaa35e381c2..43c3431369246 100644 --- a/lib/web_ui/test/engine/configuration_test.dart +++ b/lib/web_ui/test/engine/configuration_test.dart @@ -10,7 +10,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import '../matchers.dart'; +import '../common/matchers.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/embedder_test.dart b/lib/web_ui/test/engine/embedder_test.dart similarity index 100% rename from lib/web_ui/test/embedder_test.dart rename to lib/web_ui/test/engine/embedder_test.dart diff --git a/lib/web_ui/test/geometry_test.dart b/lib/web_ui/test/engine/geometry_test.dart similarity index 99% rename from lib/web_ui/test/geometry_test.dart rename to lib/web_ui/test/engine/geometry_test.dart index 80df3611c3af8..d1a9d89580d76 100644 --- a/lib/web_ui/test/geometry_test.dart +++ b/lib/web_ui/test/engine/geometry_test.dart @@ -13,7 +13,7 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart'; -import 'matchers.dart'; +import '../common/matchers.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/gesture_settings_test.dart b/lib/web_ui/test/engine/gesture_settings_test.dart similarity index 100% rename from lib/web_ui/test/gesture_settings_test.dart rename to lib/web_ui/test/engine/gesture_settings_test.dart diff --git a/lib/web_ui/test/hash_codes_test.dart b/lib/web_ui/test/engine/hash_codes_test.dart similarity index 100% rename from lib/web_ui/test/hash_codes_test.dart rename to lib/web_ui/test/engine/hash_codes_test.dart diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index 2995efe8d09ab..1b917034e913f 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -16,7 +16,7 @@ import 'package:ui/src/engine/navigation.dart'; import 'package:ui/src/engine/services.dart'; import 'package:ui/src/engine/test_embedding.dart'; -import '../spy.dart'; +import '../common/spy.dart'; Map _wrapOriginState(dynamic state) { return {'origin': true, 'state': state}; diff --git a/lib/web_ui/test/initialization_test.dart b/lib/web_ui/test/engine/initialization_test.dart similarity index 100% rename from lib/web_ui/test/initialization_test.dart rename to lib/web_ui/test/engine/initialization_test.dart diff --git a/lib/web_ui/test/keyboard_converter_test.dart b/lib/web_ui/test/engine/keyboard_converter_test.dart similarity index 99% rename from lib/web_ui/test/keyboard_converter_test.dart rename to lib/web_ui/test/engine/keyboard_converter_test.dart index a5c81b39bd96e..90922c5cbcf5e 100644 --- a/lib/web_ui/test/keyboard_converter_test.dart +++ b/lib/web_ui/test/engine/keyboard_converter_test.dart @@ -9,7 +9,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import 'keyboard_test_common.dart'; +import '../common/keyboard_test_common.dart'; const int kLocationStandard = 0; const int kLocationLeft = 1; diff --git a/lib/web_ui/test/lerp_test.dart b/lib/web_ui/test/engine/lerp_test.dart similarity index 99% rename from lib/web_ui/test/lerp_test.dart rename to lib/web_ui/test/engine/lerp_test.dart index 65dde6513fbd7..d69d5c1ffc2ea 100644 --- a/lib/web_ui/test/lerp_test.dart +++ b/lib/web_ui/test/engine/lerp_test.dart @@ -7,7 +7,7 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart'; -import 'matchers.dart'; +import '../common/matchers.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/locale_test.dart b/lib/web_ui/test/engine/locale_test.dart similarity index 100% rename from lib/web_ui/test/locale_test.dart rename to lib/web_ui/test/engine/locale_test.dart diff --git a/lib/web_ui/test/engine/platform_views/content_manager_test.dart b/lib/web_ui/test/engine/platform_views/content_manager_test.dart index 133e5e16abc5b..354471a0459aa 100644 --- a/lib/web_ui/test/engine/platform_views/content_manager_test.dart +++ b/lib/web_ui/test/engine/platform_views/content_manager_test.dart @@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import '../../matchers.dart'; +import '../../common/matchers.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 4e5027fad6645..33b4e0294196c 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -9,7 +9,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import '../keyboard_converter_test.dart'; +import 'keyboard_converter_test.dart'; const int _kNoButtonChange = -1; const PointerSupportDetector _defaultSupportDetector = PointerSupportDetector(); diff --git a/lib/web_ui/test/engine/profiler_test.dart b/lib/web_ui/test/engine/profiler_test.dart index 4ca8529cdf069..b3ca351b3c97c 100644 --- a/lib/web_ui/test/engine/profiler_test.dart +++ b/lib/web_ui/test/engine/profiler_test.dart @@ -8,7 +8,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import '../spy.dart'; +import '../common/spy.dart'; @JS('window._flutter_internal_on_benchmark') external set _onBenchmark (JSAny? object); diff --git a/lib/web_ui/test/raw_keyboard_test.dart b/lib/web_ui/test/engine/raw_keyboard_test.dart similarity index 100% rename from lib/web_ui/test/raw_keyboard_test.dart rename to lib/web_ui/test/engine/raw_keyboard_test.dart diff --git a/lib/web_ui/test/engine/recording_canvas_test.dart b/lib/web_ui/test/engine/recording_canvas_test.dart index 46dab440bcef2..72c63f4143fa6 100644 --- a/lib/web_ui/test/engine/recording_canvas_test.dart +++ b/lib/web_ui/test/engine/recording_canvas_test.dart @@ -7,8 +7,8 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart'; +import '../common/mock_engine_canvas.dart'; import '../html/screenshot.dart'; -import '../mock_engine_canvas.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/engine/routing_test.dart similarity index 99% rename from lib/web_ui/test/window_test.dart rename to lib/web_ui/test/engine/routing_test.dart index d37bdbdc2b335..dd940cd802103 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/engine/routing_test.dart @@ -10,8 +10,8 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart' hide window; import 'package:ui/ui.dart' as ui; -import 'engine/history_test.dart'; -import 'matchers.dart'; +import '../common/matchers.dart'; +import 'history_test.dart'; const MethodCodec codec = JSONMethodCodec(); diff --git a/lib/web_ui/test/engine/semantics/semantics_tester.dart b/lib/web_ui/test/engine/semantics/semantics_tester.dart index cee9eead3d3b0..0b7b7623c676f 100644 --- a/lib/web_ui/test/engine/semantics/semantics_tester.dart +++ b/lib/web_ui/test/engine/semantics/semantics_tester.dart @@ -14,7 +14,7 @@ import 'package:ui/src/engine/util.dart'; import 'package:ui/src/engine/vector_math.dart'; import 'package:ui/ui.dart' as ui; -import '../../matchers.dart'; +import '../../common/matchers.dart'; /// Gets the DOM host where the Flutter app is being rendered. /// diff --git a/lib/web_ui/test/engine/surface/frame_timings_test.dart b/lib/web_ui/test/engine/surface/frame_timings_test.dart index c0c295057dd96..901f1ffea73e2 100644 --- a/lib/web_ui/test/engine/surface/frame_timings_test.dart +++ b/lib/web_ui/test/engine/surface/frame_timings_test.dart @@ -6,7 +6,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; -import '../../frame_timings_common.dart'; +import '../../common/frame_timings_common.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/engine/surface/platform_view_test.dart b/lib/web_ui/test/engine/surface/platform_view_test.dart index 243d75025d63c..700da0c0cbe34 100644 --- a/lib/web_ui/test/engine/surface/platform_view_test.dart +++ b/lib/web_ui/test/engine/surface/platform_view_test.dart @@ -9,7 +9,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart'; -import '../../matchers.dart'; +import '../../common/matchers.dart'; const MethodCodec codec = StandardMethodCodec(); final EngineSingletonFlutterWindow window = EngineSingletonFlutterWindow(0, EnginePlatformDispatcher.instance); diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart index eddb3dce5e61c..851ea9e4ae45f 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -13,7 +13,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import '../../matchers.dart'; +import '../../common/matchers.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/text_editing_test.dart b/lib/web_ui/test/engine/text_editing_test.dart similarity index 99% rename from lib/web_ui/test/text_editing_test.dart rename to lib/web_ui/test/engine/text_editing_test.dart index 30d669bf5801c..5c84bbcdaa3e0 100644 --- a/lib/web_ui/test/text_editing_test.dart +++ b/lib/web_ui/test/engine/text_editing_test.dart @@ -21,7 +21,7 @@ import 'package:ui/src/engine/text_editing/text_editing.dart'; import 'package:ui/src/engine/util.dart'; import 'package:ui/src/engine/vector_math.dart'; -import 'spy.dart'; +import '../common/spy.dart'; /// The `keyCode` of the "Enter" key. const int _kReturnKeyCode = 13; diff --git a/lib/web_ui/test/felt_config.yaml b/lib/web_ui/test/felt_config.yaml new file mode 100644 index 0000000000000..6f19e4913e9e2 --- /dev/null +++ b/lib/web_ui/test/felt_config.yaml @@ -0,0 +1,270 @@ +# See the `README.md` in this directory for documentation on the structure of +# this file. +compile-configs: + - name: dart2js-html + compiler: dart2js + renderer: html + + - name: dart2js-canvaskit + compiler: dart2js + renderer: canvaskit + + - name: dart2js-skwasm + compiler: dart2js + renderer: skwasm + + - name: dart2wasm-html + compiler: dart2wasm + renderer: html + + - name: dart2wasm-canvaskit + compiler: dart2wasm + renderer: canvaskit + + - name: dart2wasm-skwasm + compiler: dart2wasm + renderer: skwasm + +test-sets: + # Tests for non-renderer logic + - name: engine + directory: engine + + # Tests for canvaskit-renderer-specific functionality + - name: canvaskit + directory: canvaskit + + # Tests for html-renderer-specific functionality + - name: html + directory: html + + # Tests for renderer functionality that can be run on any renderer + - name: ui + directory: ui + + # This just has a single test that makes sure the skwasm stub renderer is + # included when compiling to JS + - name: skwasm_stub + directory: skwasm_stub + +test-bundles: + - name: dart2js-html-engine + test-set: engine + compile-config: dart2js-html + + - name: dart2js-html-html + test-set: html + compile-config: dart2js-html + + - name: dart2js-html-ui + test-set: ui + compile-config: dart2js-html + + - name: dart2js-canvaskit-canvaskit + test-set: canvaskit + compile-config: dart2js-canvaskit + + - name: dart2js-canvaskit-ui + test-set: ui + compile-config: dart2js-canvaskit + + - name: dart2js-skwasm-skwasm_stub + test-set: skwasm_stub + compile-config: dart2js-skwasm + + - name: dart2wasm-html-engine + test-set: engine + compile-config: dart2wasm-html + + - name: dart2wasm-html-html + test-set: html + compile-config: dart2wasm-html + + - name: dart2wasm-html-ui + test-set: ui + compile-config: dart2wasm-html + + - name: dart2wasm-canvaskit-canvaskit + test-set: canvaskit + compile-config: dart2wasm-canvaskit + + - name: dart2wasm-canvaskit-ui + test-set: ui + compile-config: dart2wasm-canvaskit + + - name: dart2wasm-skwasm-ui + test-set: ui + compile-config: dart2wasm-skwasm + +run-configs: + - name: chrome + browser: chrome + canvaskit-variant: chromium + + - name: chrome-full + browser: chrome + canvaskit-variant: full + + - name: edge + browser: edge + canvaskit-variant: chromium + + - name: edge-full + browser: edge + canvaskit-variant: full + + - name: firefox + browser: firefox + + - name: safari + browser: safari + +test-suites: + - name: chrome-dart2js-html-engine + test-bundle: dart2js-html-engine + run-config: chrome + + - name: chrome-dart2js-html-html + test-bundle: dart2js-html-html + run-config: chrome + + - name: chrome-dart2js-html-ui + test-bundle: dart2js-html-ui + run-config: chrome + + - name: chrome-dart2js-canvaskit-canvaskit + test-bundle: dart2js-canvaskit-canvaskit + run-config: chrome + artifact-deps: [ canvaskit_chromium ] + + - name: chrome-dart2js-canvaskit-ui + test-bundle: dart2js-canvaskit-ui + run-config: chrome + artifact-deps: [ canvaskit_chromium ] + + - name: chrome-dart2js-skwasm-skwasm_stub + test-bundle: dart2js-skwasm-skwasm_stub + run-config: chrome + + - name: chrome-full-dart2js-canvaskit-canvaskit + test-bundle: dart2js-canvaskit-canvaskit + run-config: chrome-full + artifact-deps: [ canvaskit ] + + - name: chrome-full-dart2js-canvaskit-ui + test-bundle: dart2js-canvaskit-ui + run-config: chrome-full + artifact-deps: [ canvaskit ] + + - name: edge-dart2js-html-engine + test-bundle: dart2js-html-engine + run-config: edge + + - name: edge-dart2js-html-html + test-bundle: dart2js-html-html + run-config: edge + + - name: edge-dart2js-html-ui + test-bundle: dart2js-html-ui + run-config: edge + + - name: edge-dart2js-canvaskit-canvaskit + test-bundle: dart2js-canvaskit-canvaskit + run-config: edge + artifact-deps: [ canvaskit_chromium ] + + - name: edge-dart2js-canvaskit-ui + test-bundle: dart2js-canvaskit-ui + run-config: edge + artifact-deps: [ canvaskit_chromium ] + + - name: edge-full-dart2js-canvaskit-canvaskit + test-bundle: dart2js-canvaskit-canvaskit + run-config: edge-full + artifact-deps: [ canvaskit ] + + - name: edge-full-dart2js-canvaskit-ui + test-bundle: dart2js-canvaskit-ui + run-config: edge-full + artifact-deps: [ canvaskit ] + + - name: firefox-dart2js-html-engine + test-bundle: dart2js-html-engine + run-config: firefox + + - name: firefox-dart2js-html-html + test-bundle: dart2js-html-html + run-config: firefox + + - name: firefox-dart2js-html-ui + test-bundle: dart2js-html-ui + run-config: firefox + + - name: firefox-dart2js-canvaskit-canvaskit + test-bundle: dart2js-canvaskit-canvaskit + run-config: firefox + artifact-deps: [ canvaskit ] + + - name: firefox-dart2js-canvaskit-ui + test-bundle: dart2js-canvaskit-ui + run-config: firefox + artifact-deps: [ canvaskit ] + + - name: safari-dart2js-html-engine + test-bundle: dart2js-html-engine + run-config: safari + + - name: safari-dart2js-html-html + test-bundle: dart2js-html-html + run-config: safari + + - name: safari-dart2js-html-ui + test-bundle: dart2js-html-ui + run-config: safari + + - name: safari-dart2js-canvaskit-canvaskit + test-bundle: dart2js-canvaskit-canvaskit + run-config: safari + artifact-deps: [ canvaskit ] + + - name: safari-dart2js-canvaskit-ui + test-bundle: dart2js-canvaskit-ui + run-config: safari + artifact-deps: [ canvaskit ] + + - name: chrome-dart2wasm-html-engine + test-bundle: dart2wasm-html-engine + run-config: chrome + + - name: chrome-dart2wasm-html-html + test-bundle: dart2wasm-html-html + run-config: chrome + + - name: chrome-dart2wasm-html-ui + test-bundle: dart2wasm-html-ui + run-config: chrome + + - name: chrome-dart2wasm-canvaskit-canvaskit + test-bundle: dart2wasm-canvaskit-canvaskit + run-config: chrome + artifact-deps: [ canvaskit_chromium ] + + - name: chrome-dart2wasm-canvaskit-ui + test-bundle: dart2wasm-canvaskit-ui + run-config: chrome + artifact-deps: [ canvaskit_chromium ] + + - name: chrome-dart2wasm-skwasm-ui + test-bundle: dart2wasm-skwasm-ui + run-config: chrome + artifact-deps: [ skwasm ] + + - name: chrome-full-dart2wasm-canvaskit-canvaskit + test-bundle: dart2wasm-canvaskit-canvaskit + run-config: chrome-full + artifact-deps: [ canvaskit ] + + - name: chrome-full-dart2wasm-canvaskit-ui + test-bundle: dart2wasm-canvaskit-ui + run-config: chrome-full + artifact-deps: [ canvaskit ] diff --git a/lib/web_ui/test/html/compositing/compositing_golden_test.dart b/lib/web_ui/test/html/compositing/compositing_golden_test.dart index fd4e3d4791ebb..451c468a2222b 100644 --- a/lib/web_ui/test/html/compositing/compositing_golden_test.dart +++ b/lib/web_ui/test/html/compositing/compositing_golden_test.dart @@ -10,7 +10,7 @@ import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import 'package:web_engine_tester/golden_tester.dart'; -import '../../matchers.dart'; +import '../../common/matchers.dart'; const ui.Rect region = ui.Rect.fromLTWH(0, 0, 500, 100); diff --git a/lib/web_ui/test/html/path_metrics_golden_test.dart b/lib/web_ui/test/html/path_metrics_golden_test.dart index 49410f5f9c70b..4a3ea96f7288d 100644 --- a/lib/web_ui/test/html/path_metrics_golden_test.dart +++ b/lib/web_ui/test/html/path_metrics_golden_test.dart @@ -7,7 +7,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' hide TextStyle; -import '../matchers.dart'; +import '../common/matchers.dart'; import 'screenshot.dart'; void main() { diff --git a/lib/web_ui/test/path_test.dart b/lib/web_ui/test/html/path_test.dart similarity index 99% rename from lib/web_ui/test/path_test.dart rename to lib/web_ui/test/html/path_test.dart index 3165c5fba13f6..416e1973a6b55 100644 --- a/lib/web_ui/test/path_test.dart +++ b/lib/web_ui/test/html/path_test.dart @@ -10,7 +10,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' hide window; -import 'matchers.dart'; +import '../common/matchers.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/html/recording_canvas_golden_test.dart b/lib/web_ui/test/html/recording_canvas_golden_test.dart index b032c45ccc0e0..3a32e02830ed7 100644 --- a/lib/web_ui/test/html/recording_canvas_golden_test.dart +++ b/lib/web_ui/test/html/recording_canvas_golden_test.dart @@ -11,7 +11,7 @@ import 'package:ui/src/engine.dart' hide ColorSpace; import 'package:ui/ui.dart' hide TextStyle; import 'package:web_engine_tester/golden_tester.dart'; -import '../matchers.dart'; +import '../common/matchers.dart'; import 'screenshot.dart'; void main() { diff --git a/lib/web_ui/test/text_test.dart b/lib/web_ui/test/html/text_test.dart similarity index 99% rename from lib/web_ui/test/text_test.dart rename to lib/web_ui/test/html/text_test.dart index adf768bd6507d..2b7e629fb2917 100644 --- a/lib/web_ui/test/text_test.dart +++ b/lib/web_ui/test/html/text_test.dart @@ -11,8 +11,8 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart'; -import 'html/paragraph/helper.dart'; -import 'matchers.dart'; +import '../common/matchers.dart'; +import 'paragraph/helper.dart'; void main() { internalBootstrapBrowserTest(() => testMain); diff --git a/lib/web_ui/test/skwasm/smoke_test.dart b/lib/web_ui/test/skwasm_stub/smoke_test.dart similarity index 78% rename from lib/web_ui/test/skwasm/smoke_test.dart rename to lib/web_ui/test/skwasm_stub/smoke_test.dart index 103af64316d40..74c5a30ad7ab9 100644 --- a/lib/web_ui/test/skwasm/smoke_test.dart +++ b/lib/web_ui/test/skwasm_stub/smoke_test.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import 'package:ui/src/engine/browser_detection.dart'; import 'package:ui/src/engine/renderer.dart'; import 'package:ui/src/engine/skwasm/skwasm_stub/renderer.dart'; @@ -23,8 +22,6 @@ Future testMain() async { expect(() { renderer.initialize(); }, throwsUnimplementedError); - }, skip: isWasm); - // This test is specifically designed for the JS case, to make sure we - // compile to the skwasm stub renderer. + }); }); } diff --git a/lib/web_ui/test/ui/canvas_test.dart b/lib/web_ui/test/ui/canvas_test.dart index 4c294778e040c..9bba8d1c16922 100644 --- a/lib/web_ui/test/ui/canvas_test.dart +++ b/lib/web_ui/test/ui/canvas_test.dart @@ -10,7 +10,7 @@ import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; -import '../matchers.dart'; +import '../common/matchers.dart'; import 'utils.dart'; void main() { @@ -18,7 +18,7 @@ void main() { } Future testMain() async { - setUpUiTest(); + await setUpUiTest(); final bool deviceClipRoundsOut = renderer is! HtmlRenderer; runCanvasTests(deviceClipRoundsOut: deviceClipRoundsOut); diff --git a/lib/web_ui/test/ui/color_test.dart b/lib/web_ui/test/ui/color_test.dart index 83190e5c3d6ce..05a7f66cbae14 100644 --- a/lib/web_ui/test/ui/color_test.dart +++ b/lib/web_ui/test/ui/color_test.dart @@ -16,8 +16,8 @@ class NotAColor extends Color { const NotAColor(super.value); } -void testMain() { - setUpUiTest(); +Future testMain() async { + await setUpUiTest(); test('color accessors should work', () { const Color foo = Color(0x12345678); diff --git a/lib/web_ui/test/gradient_test.dart b/lib/web_ui/test/ui/gradient_test.dart similarity index 92% rename from lib/web_ui/test/gradient_test.dart rename to lib/web_ui/test/ui/gradient_test.dart index 667e286c0106a..6bbb840abded0 100644 --- a/lib/web_ui/test/gradient_test.dart +++ b/lib/web_ui/test/ui/gradient_test.dart @@ -7,11 +7,15 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart'; +import 'utils.dart'; + void main() { internalBootstrapBrowserTest(() => testMain); } -void testMain() { +Future testMain() async { + await setUpUiTest(); + test('Gradient.radial with no focal point', () { expect( Gradient.radial( @@ -22,7 +26,7 @@ void testMain() { TileMode.mirror), isNotNull, ); - }); + }, skip: isSkwasm); // this is just a radial gradient, focal point is discarded. test('radial center and focal == Offset.zero and focalRadius == 0.0 is ok', @@ -38,7 +42,7 @@ void testMain() { Offset.zero, ), isNotNull); - }); + }, skip: isSkwasm); test('radial center != focal and focalRadius == 0.0 is ok', () { expect( @@ -52,7 +56,7 @@ void testMain() { const Offset(2.0, 2.0), ), isNotNull); - }); + }, skip: isSkwasm); // this would result in div/0 on skia side. test('radial center and focal == Offset.zero and focalRadius != 0.0 assert', @@ -70,5 +74,5 @@ void testMain() { ), throwsA(const TypeMatcher()), ); - }); + }, skip: isSkwasm); } diff --git a/lib/web_ui/test/paragraph_builder_test.dart b/lib/web_ui/test/ui/paragraph_builder_test.dart similarity index 79% rename from lib/web_ui/test/paragraph_builder_test.dart rename to lib/web_ui/test/ui/paragraph_builder_test.dart index a795a35adf686..6540cb2ccde4f 100644 --- a/lib/web_ui/test/paragraph_builder_test.dart +++ b/lib/web_ui/test/ui/paragraph_builder_test.dart @@ -6,12 +6,14 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart'; +import 'utils.dart'; + void main() { internalBootstrapBrowserTest(() => testMain); } Future testMain() async { - await webOnlyInitializePlatform(); + await setUpUiTest(); test('Should be able to build and layout a paragraph', () { final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle()); @@ -22,14 +24,7 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: 800.0)); expect(paragraph.width, isNonZero); expect(paragraph.height, isNonZero); - }); - - test('pushStyle should not segfault after build()', () { - final ParagraphBuilder paragraphBuilder = - ParagraphBuilder(ParagraphStyle()); - paragraphBuilder.build(); - paragraphBuilder.pushStyle(TextStyle()); - }); + }, skip: isSkwasm); test('the presence of foreground style should not throw', () { final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle()); @@ -39,5 +34,5 @@ Future testMain() async { builder.addText('hi'); expect(() => builder.build(), returnsNormally); - }); + }, skip: isSkwasm); } diff --git a/lib/web_ui/test/ui/path_metrics_test.dart b/lib/web_ui/test/ui/path_metrics_test.dart index 006c59df2450e..69d0095a8e714 100644 --- a/lib/web_ui/test/ui/path_metrics_test.dart +++ b/lib/web_ui/test/ui/path_metrics_test.dart @@ -8,7 +8,7 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart'; -import '../matchers.dart'; +import '../common/matchers.dart'; import 'utils.dart'; const double kTolerance = 0.1; @@ -17,8 +17,8 @@ void main() { internalBootstrapBrowserTest(() => testMain); } -void testMain() { - setUpUiTest(); +Future testMain() async { + await setUpUiTest(); group('PathMetric length', () { test('empty path', () { final Path path = Path(); diff --git a/lib/web_ui/test/ui/path_test.dart b/lib/web_ui/test/ui/path_test.dart index 444e591612154..10769f8b94eb6 100644 --- a/lib/web_ui/test/ui/path_test.dart +++ b/lib/web_ui/test/ui/path_test.dart @@ -14,8 +14,8 @@ void main() { internalBootstrapBrowserTest(() => testMain); } -void testMain() { - setUpUiTest(); +Future testMain() async { + await setUpUiTest(); test('path getBounds', () { const Rect r = Rect.fromLTRB(1.0, 3.0, 5.0, 7.0); final Path p = Path()..addRect(r); diff --git a/lib/web_ui/test/ui/picture_test.dart b/lib/web_ui/test/ui/picture_test.dart index daa9f9d8075c9..576b0f1746cc6 100644 --- a/lib/web_ui/test/ui/picture_test.dart +++ b/lib/web_ui/test/ui/picture_test.dart @@ -13,7 +13,7 @@ void main() { } Future testMain() async { - setUpUiTest(); + await setUpUiTest(); test('Picture construction invokes onCreate once', () async { int onCreateInvokedCount = 0; diff --git a/lib/web_ui/test/ui/rect_test.dart b/lib/web_ui/test/ui/rect_test.dart index 156b6fc84363b..9bc14cf25eb33 100644 --- a/lib/web_ui/test/ui/rect_test.dart +++ b/lib/web_ui/test/ui/rect_test.dart @@ -12,8 +12,8 @@ void main() { internalBootstrapBrowserTest(() => testMain); } -void testMain() { - setUpUiTest(); +Future testMain() async { + await setUpUiTest(); test('rect accessors', () { const Rect r = Rect.fromLTRB(1.0, 3.0, 5.0, 7.0); expect(r.left, equals(1.0)); diff --git a/lib/web_ui/test/ui/rrect_test.dart b/lib/web_ui/test/ui/rrect_test.dart index bdf84f1cc17a6..c82c689cc7e7e 100644 --- a/lib/web_ui/test/ui/rrect_test.dart +++ b/lib/web_ui/test/ui/rrect_test.dart @@ -12,8 +12,8 @@ void main() { internalBootstrapBrowserTest(() => testMain); } -void testMain() { - setUpUiTest(); +Future testMain() async { + await setUpUiTest(); test('RRect.contains()', () { final RRect rrect = RRect.fromRectAndCorners( const Rect.fromLTRB(1.0, 1.0, 2.0, 2.0), diff --git a/lib/web_ui/test/ui/title_test.dart b/lib/web_ui/test/ui/title_test.dart index c7caa806f5363..f9c532c7f5623 100644 --- a/lib/web_ui/test/ui/title_test.dart +++ b/lib/web_ui/test/ui/title_test.dart @@ -13,8 +13,8 @@ void main() { internalBootstrapBrowserTest(() => testMain); } -void testMain() { - setUpUiTest(); +Future testMain() async { + await setUpUiTest(); const MethodCodec codec = JSONMethodCodec(); diff --git a/lib/web_ui/test/ui/utils.dart b/lib/web_ui/test/ui/utils.dart index da6c5ffb720d7..312d5e4bc07ea 100644 --- a/lib/web_ui/test/ui/utils.dart +++ b/lib/web_ui/test/ui/utils.dart @@ -3,13 +3,16 @@ // found in the LICENSE file. import 'package:ui/src/engine.dart'; +import 'package:ui/src/engine/skwasm/skwasm_stub.dart' if (dart.library.ffi) 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import '../canvaskit/common.dart'; /// Initializes the renderer for this test. -void setUpUiTest() { - if (renderer is CanvasKitRenderer) { +Future setUpUiTest() async { + if (isCanvasKit) { setUpCanvasKitTest(); + } else if (isHtml) { + await initializeEngine(); } } @@ -18,3 +21,5 @@ bool get isCanvasKit => renderer is CanvasKitRenderer; /// Returns [true] if this test is running in the HTML renderer. bool get isHtml => renderer is HtmlRenderer; + +bool get isSkwasm => renderer is SkwasmRenderer; diff --git a/web_sdk/web_test_utils/lib/image_compare.dart b/web_sdk/web_test_utils/lib/image_compare.dart index c2ac523552db8..4212ae642ae46 100644 --- a/web_sdk/web_test_utils/lib/image_compare.dart +++ b/web_sdk/web_test_utils/lib/image_compare.dart @@ -24,6 +24,7 @@ Future compareImage( String filename, SkiaGoldClient? skiaClient, { required bool isCanvaskitTest, + required bool verbose, }) async { if (skiaClient == null) { return 'OK'; @@ -69,12 +70,13 @@ Future compareImage( // At the moment, we don't support local screenshot testing because we use // Skia Gold to handle our screenshots and diffing. In the future, we might // implement local screenshot testing if there's a need. - print('Screenshot generated: file://$screenshotPath'); // ignore: avoid_print return 'OK'; } // TODO(mdebbar): Use the Gold tool to locally diff the golden. - + if (verbose) { + print('Screenshot generated: file://$screenshotPath'); // ignore: avoid_print + } return 'OK'; }