8000 fix(workspace): isolate host and device integration test roots by shroominic · Pull Request #141 · shroominic/OpenGlucose · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/check-tooling.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ if [ -f "$repo_root/openhealth/fastlane/Fastfile" ]; then
ruby "$repo_root/scripts/test-external-testflight-release-contract.rb"
ruby "$repo_root/scripts/test-notification-receipt.rb"
ruby "$repo_root/scripts/test-internal-testflight-policy.rb"
ruby "$repo_root/scripts/test-workspace-integration-runner.rb"
ruby "$repo_root/openhealth/scripts/test-testflight-signing-contract.rb"
fi
48 changes: 46 additions & 2 deletions scripts/flutter-workspace.sh
8000
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,19 @@ for_project() {
done
}

# Host lane: directory-marked tests under test/integration plus any test file
# tagged with the `integration` tag. A single `flutter test` invocation rejects
# a mix of this lane and the device-backed integration_test/ root, so the two
# are discovered and run separately.
integration_test_files() {
project_path=$1
for test_root in test integration_test; do
project_dir=$2
test_roots="test"
# For Flutter projects the integration_test/ root is device-backed and runs
# in its own lane; for plain Dart packages it stays an ordinary test root.
uses_flutter "$project_dir" || test_roots="test integration_test"

for test_root in $test_roots; do
[ ! -d "$project_path/$test_root" ] ||
find "$project_path/$test_root" -type f -name '*_test.dart' -print
done | sort -u | while IFS= read -r test_file; do
Expand All @@ -132,6 +142,19 @@ integration_test_files() {
done
}

# Device lane: integration_test/ holds device-backed harnesses that
# `flutter test` can only run against hardware named by FLUTTER_TEST_DEVICE_ID.
device_integration_files() {
project_path=$1
project_dir=$2
uses_flutter "$project_dir" || return 0
[ -d "$project_path/integration_test" ] || return 0
find "$project_path/integration_test" -type f -name '*_test.dart' -print | sort |
while IFS= read -r test_file; do
printf '%s\n' "${test_file#"$project_path/"}"
done
}

run_project_tests() {
project_dir=$1
shift
Expand All @@ -150,7 +173,7 @@ run_integration_tests() {

for project_dir in $project_dirs; do
project_path="$repo_root/$project_dir"
discovered_files=$(integration_test_files "$project_path")
discovered_files=$(integration_test_files "$project_path" "$project_dir")
if [ -n "$discovered_files" ]; then
set --
previous_ifs=$IFS
Expand All @@ -166,6 +189,27 @@ run_integration_tests() {
found=true
run_project_tests "$project_dir" "$@"
fi

device_files=$(device_integration_files "$project_path" "$project_dir")
if [ -n "$device_files" ]; then
set --
previous_ifs=$IFS
IFS='
'
for test_file in $device_files; do
set -- "$@" "$test_file"
done
IFS=$previous_ifs

found=true
printf '\n==> %s: device-backed integration tests\n' "$project_dir"
printf ' %s\n' "$@"
if [ -n "${FLUTTER_TEST_DEVICE_ID:-}" ]; then
run_project_tests "$project_dir" --device-id "$FLUTTER_TEST_DEVICE_ID" "$@"
else
printf ' deferred: no device named; set FLUTTER_TEST_DEVICE_ID=<device-id> to run these on hardware.\n'
fi
fi
done

[ "$found" = true ] || die 'no tagged or directory-based integration tests were found'
Expand Down
161 changes: 161 additions & 0 deletions scripts/test-workspace-integration-runner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env ruby

# Pins the `test-integration` runner contract in scripts/flutter-workspace.sh.
#
# The runner used to hand both test roots to one `flutter test` call, which
# Flutter rejects outright:
#
# Integration tests and unit tests cannot be run in a single invocation.
#
# These checks drive the real script against a throwaway fixture repository
# with stubbed Flutter tooling, so they run on any host and fail if the roots
# are ever mixed again.

require "fileutils"
require "open3"
require "tmpdir"

SCRIPT_NAME = "flutter-workspace.sh"

FLUTTER_STUB = <<~'SH'
#!/bin/sh
printf '%s\n' "flutter $*" >> "$RUNNER_STUB_LOG"
case "$*" in
*--version*) printf '{"frameworkVersion":"3.41.6"}\n' ;;
esac
exit 0
SH

DART_STUB = <<~'SH'
#!/bin/sh
printf 'Dart SDK version: 3.11.4 (stable) on "macos"\n' >&2
exit 0
SH

def assert(condition, message)
raise "assertion failed: #{message}" unless condition
end

Result = Struct.new(:status, :output, :log) do
def test_invocations
log.lines.map(&:chomp).select { |line| line.start_with?("flutter test") }
end
end

def fixture(host_tests: true, device_tests: false)
Dir.mktmpdir("workspace-integration-runner") do |root|
FileUtils.mkdir_p(File.join(root, "scripts"))
FileUtils.mkdir_p(File.join(root, "bin"))
FileUtils.cp(File.expand_path(SCRIPT_NAME, __dir__), File.join(root, "scripts", SCRIPT_NAME))

{ "flutter" => FLUTTER_STUB, "dart" => DART_STUB }.each do |name, contents|
stub = File.join(root, "bin", name)
File.write(stub, contents)
FileUtils.chmod(0o755, stub)
end

app = File.join(root, "openhealth")
FileUtils.mkdir_p(app)
File.write(File.join(app, "pubspec.yaml"), "name: openhealth\nenvironment:\n sdk: flutter\n")

if host_tests
FileUtils.mkdir_p(File.join(app, "test", "integration"))
File.write(File.join(app, "test", "integration", "demo_test.dart"), "void main() {}\n")
end

if device_tests
FileUtils.mkdir_p(File.join(app, "integration_test"))
File.write(File.join(app, "integration_test", "device_test.dart"), "void main() {}\n")
end

yield root
end
end

def run_runner(root, device_id: nil)
log = File.join(root, "stub.log")
File.write(log, "")
env = {
"PATH" => "#{File.join(root, 'bin')}:#{ENV['PATH']}",
"RUNNER_STUB_LOG" => log,
"FLUTTER_TEST_DEVICE_ID" => device_id,
}
output, status = Open3.capture2e(
env,
"sh",
File.join(root, "scripts", SCRIPT_NAME),
"test-integration",
chdir: root
)
Result.new(status.exitstatus, output, File.read(log))
end

HOST_TEST = "test/integration/demo_test.dart"
DEVICE_TEST = "integration_test/device_test.dart"

# Both roots present, no device named: the host root runs, the device root is
# deferred with the exact knob that enables it, and nothing mixes the roots.
fixture(device_tests: true) do |root|
result = run_runner(root)
assert(result.status == 0, "expected exit 0, got #{result.status}:\n#{result.output}")

host_runs = result.test_invocations.select { |line| line.include?(HOST_TEST) }
device_runs = result.test_invocations.select { |line| line.include?(DEVICE_TEST) }
assert(host_runs.length == 1, "expected one host-root run, got #{host_runs.inspect}")
assert(device_runs.empty?, "device root must not run without FLUTTER_TEST_DEVICE_ID: #{device_runs.inspect}")

result.test_invocations.each do |line|
assert(
!(line.include?("test/integration/") && line.include?("integration_test/")),
"invocation mixed both test roots: #{line}"
)
end

assert(
result.output.include?("FLUTTER_TEST_DEVICE_ID"),
"deferral notice must name the enabling variable:\n#{result.output}"
)
assert(
result.output.include?(" #{DEVICE_TEST}\n"),
"deferral notice must list the deferred files by repository-relative path:\n#{result.output}"
)
end

# A named device routes the device-backed root to its own invocation.
fixture(device_tests: true) do |root|
result = run_runner(root, device_id: "pixel-test-1")
assert(result.status == 0, "expected exit 0, got #{result.status}:\n#{result.output}")

device_runs = result.test_invocations.select { |line| line.include?(DEVICE_TEST) }
assert(device_runs.length == 1, "expected one device-root run, got #{device_runs.inspect}")
assert(
device_runs.first.include?("--device-id pixel-test-1"),
"device root must target the named device: #{device_runs.first}"
)
end

# Host root only: unchanged behaviour, and no device lane is announced.
fixture do |root|
result = run_runner(root)
assert(result.status == 0, "expected exit 0, got #{result.status}:\n#{result.output}")
assert(
result.test_invocations == ["flutter test --no-pub #{HOST_TEST}"],
"host-only tree must produce one host invocation, got #{result.test_invocations.inspect}"
)
assert(
!result.output.include?("FLUTTER_TEST_DEVICE_ID"),
"no device lane should be announced when the root is absent:\n#{result.output}"
)
end

# No roots at all: the existing "nothing found" contract still fails closed.
fixture(host_tests: false) do |root|
result = run_runner(root)
assert(result.status != 0, "expected a non-zero exit when no tests are found:\n#{result.output}")
assert(
result.output.include?("no tagged or directory-based integration tests were found"),
"expected the missing-tests diagnostic:\n#{result.output}"
)
end

puts "Workspace integration runner checks passed."
0