diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/lib/check_readiness.dart b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/lib/check_readiness.dart index d64f5cd7762e..18e41ac01dc0 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/lib/check_readiness.dart +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/lib/check_readiness.dart @@ -45,6 +45,9 @@ class ReadinessChecker { if (!await _checkDependencies(workspaceRoot)) { isReady = false; } + if (!await _activateFlutterPluginTools(workspaceRoot)) { + isReady = false; + } } if (isReady) { @@ -137,4 +140,48 @@ class ReadinessChecker { _log('Dependencies are resolved and ready.'); return true; } + + Future _activateFlutterPluginTools(String workspaceRoot) async { + _log('5. Activating flutter_plugin_tools...'); + final String? repoRoot = _findRepoRoot(workspaceRoot); + if (repoRoot == null) { + _log('Error: Failed to find repository root (no .git directory found).'); + return false; + } + final ProcessResult activateResult = await _processManager.run( + [ + 'dart', + 'pub', + 'global', + 'activate', + '--source', + 'path', + _fileSystem.path.join(repoRoot, 'script', 'tool') + ], + workingDirectory: workspaceRoot, + ); + if (activateResult.exitCode != 0) { + _log('Error: Failed to globally activate flutter_plugin_tools.'); + _log(activateResult.stderr); + return false; + } + _log('flutter_plugin_tools activated successfully.'); + return true; + } + + String? _findRepoRoot(String startPath) { + Directory dir = _fileSystem.directory(startPath); + while (true) { + final String gitPath = _fileSystem.path.join(dir.path, '.git'); + if (_fileSystem.typeSync(gitPath) != FileSystemEntityType.notFound) { + return dir.path; + } + final Directory parent = dir.parent; + if (parent.path == dir.path) { + break; + } + dir = parent; + } + return null; + } } diff --git a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/test/check_test.dart b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/test/check_test.dart index f6a192043a2d..c8ffc10f0f0e 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/check-readiness/test/check_test.dart +++ b/packages/camera/camera_android_camerax/.agents/skills/check-readiness/test/check_test.dart @@ -67,6 +67,7 @@ void main() { log: (Object? message) => printLogs.add(message.toString()), ); workspaceRoot = fileSystem.path.absolute('workspace'); + fileSystem.file(fileSystem.path.join(workspaceRoot, '.git')).createSync(recursive: true); printLogs.clear(); }); @@ -188,6 +189,9 @@ void main() { log: (Object? message) => printLogs.add(message.toString()), ); winWorkspaceRoot = r'C:\workspace'; + winFileSystem + .file(winFileSystem.path.join(winWorkspaceRoot, '.git')) + .createSync(recursive: true); printLogs.clear(); }); diff --git a/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/SKILL.md b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/SKILL.md index 8816dcad45d3..d36e8d00d478 100644 --- a/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/SKILL.md +++ b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/SKILL.md @@ -65,16 +65,25 @@ and then merge conflicts must be resolved. Tests ensure that your changes do not break existing functionality and that new features work as expected. -All unit tests must pass before code can be merged. +All unit tests (both Dart and native Android) must pass before code can be merged. + +### Dart Unit Tests Command to run: ```bash -cd $(git rev-parse --show-toplevel) -dart run script/tool/bin/flutter_plugin_tools.dart \ +dart pub global run flutter_plugin_tools \ dart-test --packages camera_android_camerax ``` -If this command fails, the code is likely not ready to push. +### Native Unit Tests +Command to run: + +```bash +dart pub global run flutter_plugin_tools \ + native-test --android --packages camera_android_camerax --no-integration +``` + +If either command fails, the code is not ready to push. The tests might have been failing prior to any changes being made, so prompt the user to review all found errors and fix the newly introduced failures before pushing any code. @@ -88,8 +97,7 @@ and add a corresponding entry describing the change in `CHANGELOG.md`. Command to run: ```bash -cd $(git rev-parse --show-toplevel) -dart run script/tool/bin/flutter_plugin_tools.dart \ +dart pub global run flutter_plugin_tools \ publish-check --packages camera_android_camerax ``` @@ -104,8 +112,7 @@ the standard copyright and license header. Command to run: ```bash -cd $(git rev-parse --show-toplevel) -dart run script/tool/bin/flutter_plugin_tools.dart license-check --packages camera_android_camerax +dart pub global run flutter_plugin_tools license-check --packages camera_android_camerax ``` If this command fails, the code WAS NOT ready to push. @@ -123,6 +130,10 @@ Virtually all changes require a test. See [Test Documentation](https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md). Evaluate the change against that testing rubric. +Specifically check: +- **Dart changes**: If Dart source files in `lib/` (excluding generated files, e.g., `.g.dart` files) were modified or added, verify that corresponding Dart tests in `test/` were added or updated. +- **Native Android changes**: If native Android source files (`.java`, `.kt` in `android/src/main/`, excluding Pigeon generated files like `.g.java` and `.g.kt`) were modified or added, verify that corresponding native unit tests in `android/src/test/` were added or updated. + Based on the rubric, if the change requires a test, give the user a quote from the testing documentation on what type of test is required for their changes. diff --git a/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/evals.json b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/evals.json new file mode 100644 index 000000000000..fccbe7d33476 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/evals.json @@ -0,0 +1,59 @@ +{ + "repo_criteria": [], + "evals": [ + { + "id": 1, + "prompt": "First, run 'dart run .agents/skills/pre-push-skill/evals/test_data/setup_missing_native_test.dart'. Then, run the /pre-push skill. Do not attempt to fix any issues you find, only report the output of the skill.", + "expected_chat_output": [ + "Agent explicitly states that the code is not ready to push.", + "Agent points out that DummyEvalFeature.java was modified.", + "Agent points out that a corresponding native unit test was not added or updated." + ], + "expected_repo_state": [ + "No changes are pushed to the remote repository.", + "The agent does not create any new commits or modify the working tree." + ], + "agent_config": "reidbaker-agent" + }, + { + "id": 2, + "prompt": "First, run 'dart run .agents/skills/pre-push-skill/evals/test_data/setup_success_native_test.dart'. Then, run the /pre-push skill. Do not attempt to fix any issues you find, only report the output of the skill.", + "expected_chat_output": [ + "Agent explicitly states the code is ready to push.", + "Agent confirms that native unit tests pass." + ], + "expected_repo_state": [ + "No changes are pushed to the remote repository.", + "The agent does not create any new commits or modify the working tree." + ], + "agent_config": "reidbaker-agent" + }, + { + "id": 3, + "prompt": "First, run 'dart run .agents/skills/pre-push-skill/evals/test_data/setup_mixed_missing_native_test.dart'. Then, run the /pre-push skill. Do not attempt to fix any issues you find, only report the output of the skill.", + "expected_chat_output": [ + "Agent explicitly states that the code is not ready to push.", + "Agent points out that DummyEvalFeature.java lacks a corresponding native unit test update." + ], + "expected_repo_state": [ + "No changes are pushed to the remote repository.", + "The agent does not create any new commits or modify the working tree." + ], + "agent_config": "reidbaker-agent" + }, + { + "id": 4, + "prompt": "First, run 'dart run .agents/skills/pre-push-skill/evals/test_data/setup_missing_native_test.dart'. The Java change is just a comment and I explicitly approve bypassing the native test requirement. Then, run the /pre-push skill. Do not attempt to fix any issues you find, only report the output of the skill.", + "expected_chat_output": [ + "Agent acknowledges the user's explicit approval to bypass the native test requirement.", + "Agent states that the code is ready to push.", + "Agent lists the steps that passed or were explicitly bypassed." + ], + "expected_repo_state": [ + "No changes are pushed to the remote repository.", + "The agent does not create any new commits or modify the working tree." + ], + "agent_config": "reidbaker-agent" + } + ] +} diff --git a/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_missing_native_test.dart b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_missing_native_test.dart new file mode 100644 index 000000000000..566faf84174b --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_missing_native_test.dart @@ -0,0 +1,39 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +void main() { + final ProcessResult branchResult = Process.runSync('git', ['branch', '--show-current']); + final String branch = branchResult.stdout.toString().trim(); + if (branch == 'main') { + stdout.writeln('Error: Cannot run setup scripts on main branch.'); + exit(1); + } + + final javaFile = File('android/src/main/java/io/flutter/plugins/camerax/DummyEvalFeature.java'); + javaFile.createSync(recursive: true); + javaFile.writeAsStringSync(''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +public class DummyEvalFeature { + public void doNothing() {} +} +'''); + + Process.runSync('git', ['add', javaFile.path]); + Process.runSync('git', [ + '-c', + 'user.name=Author', + '-c', + 'user.email=author@example.com', + 'commit', + '-m', + 'Add DummyEvalFeature.java without tests', + ]); +} diff --git a/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_mixed_missing_native_test.dart b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_mixed_missing_native_test.dart new file mode 100644 index 000000000000..794d0fd8c7a3 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_mixed_missing_native_test.dart @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +void main() { + final ProcessResult branchResult = Process.runSync('git', ['branch', '--show-current']); + final String branch = branchResult.stdout.toString().trim(); + if (branch == 'main') { + stdout.writeln('Error: Cannot run setup scripts on main branch.'); + exit(1); + } + + final javaFile = File('android/src/main/java/io/flutter/plugins/camerax/DummyEvalFeature.java'); + javaFile.createSync(recursive: true); + javaFile.writeAsStringSync(''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +public class DummyEvalFeature { + public void doNothing() {} +} +'''); + + final dartFile = File('lib/src/dummy_eval_feature.dart'); + dartFile.createSync(recursive: true); + dartFile.writeAsStringSync(''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A dummy eval feature +class DummyEvalFeature { + /// Do nothing + void doNothing() {} +} +'''); + + final dartTestFile = File('test/dummy_eval_feature_test.dart'); + dartTestFile.createSync(recursive: true); + dartTestFile.writeAsStringSync(''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:camera_android_camerax/src/dummy_eval_feature.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('dummy', () { + final feature = DummyEvalFeature(); + feature.doNothing(); + }); +} +'''); + + Process.runSync('git', ['add', javaFile.path, dartFile.path, dartTestFile.path]); + Process.runSync('git', [ + '-c', + 'user.name=Author', + '-c', + 'user.email=author@example.com', + 'commit', + '-m', + 'Add DummyEvalFeature with Dart test but missing Java test', + ]); +} diff --git a/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_success_native_test.dart b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_success_native_test.dart new file mode 100644 index 000000000000..50a5cbb7b584 --- /dev/null +++ b/packages/camera/camera_android_camerax/.agents/skills/pre-push-skill/evals/test_data/setup_success_native_test.dart @@ -0,0 +1,63 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +void main() { + final ProcessResult branchResult = Process.runSync('git', ['branch', '--show-current']); + final String branch = branchResult.stdout.toString().trim(); + if (branch == 'main') { + stdout.writeln('Error: Cannot run setup scripts on main branch.'); + exit(1); + } + + final javaFile = File('android/src/main/java/io/flutter/plugins/camerax/DummyEvalFeature.java'); + javaFile.createSync(recursive: true); + javaFile.writeAsStringSync(''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +public class DummyEvalFeature { + public void doNothing() {} +} +'''); + + final javaTestFile = File( + 'android/src/test/java/io/flutter/plugins/camerax/DummyEvalFeatureTest.java', + ); + javaTestFile.createSync(recursive: true); + javaTestFile.writeAsStringSync(''' +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.camerax; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; + +public class DummyEvalFeatureTest { + @Test + public void testDoNothing() { + DummyEvalFeature feature = new DummyEvalFeature(); + feature.doNothing(); + assertTrue(true); + } +} +'''); + + Process.runSync('git', ['add', javaFile.path, javaTestFile.path]); + Process.runSync('git', [ + '-c', + 'user.name=Author', + '-c', + 'user.email=author@example.com', + 'commit', + '-m', + 'Add DummyEvalFeature.java and tests', + ]); +} diff --git a/packages/camera/camera_android_camerax/AGENTS.md b/packages/camera/camera_android_camerax/AGENTS.md index 294fd4efb372..db4d09c7f1bb 100644 --- a/packages/camera/camera_android_camerax/AGENTS.md +++ b/packages/camera/camera_android_camerax/AGENTS.md @@ -10,7 +10,10 @@ - Mocks (`dart run build_runner build -d`): Run after modifying mocked classes or adding new mocks. (see [dart-generate-test-mocks](.agents/skills/dart-generate-test-mocks/SKILL.md)) - **Verify Tests**: All tests must pass before landing. Add or update tests for - any new logic. For integration tests, see [flutter-add-integration-test](.agents/skills/flutter-add-integration-test/SKILL.md). + any new logic. + - **Dart Unit Tests**: See [dart-add-unit-test](.agents/skills/dart-add-unit-test/SKILL.md). + - **Native Unit Tests**: Run `dart pub global run flutter_plugin_tools native-test --android --packages camera_android_camerax --no-integration`. + - **Integration Tests**: See [flutter-add-integration-test](.agents/skills/flutter-add-integration-test/SKILL.md). - **Run Pre-Push Checks**: Run [pre-push-skill](.agents/skills/pre-push-skill/SKILL.md) before pushing to prevent CI failures and code review blocks. @@ -24,5 +27,6 @@ and [dart-collect-coverage](.agents/skills/dart-collect-coverage/SKILL.md). - Avoid duplicating constant strings; reuse existing ones from adjacent code. - **Testing Guidelines**: You MUST read and follow all rules in [TESTING.md](TESTING.md) BEFORE writing or modifying any tests. This is CRITICAL for preventing CI flakiness. +- **Native Unit Tests**: When modifying `.java` or `.kt` files in `android/src/main/` (excluding Pigeon generated files like `.g.java` or `.g.kt`) with logic changes, you MUST add or update corresponding native unit test files in `android/src/test/`. - **CRITICAL**: When spawning subagents, NEVER provide absolute file paths in prompts. ALWAYS use relative paths. Passing absolute paths breaks `Workspace: branch` isolation and causes state bleed into the active workspace. - **Validation**: Never run `.ci/scripts/*` or `script/tool_runner.sh` globally to validate local changes. They are slow and modify the entire repository. Always use targeted skills (like `dart-run-static-analysis` or `pre-push-skill`) to run the repo tool scoped to this package.