Make health testable (#224)

* Add `ignore` flag to health workflows

* Switch to glob

* Fix multiline

* Add default glob

* Delete multiline

* Fix errors

* Fix health commenting

* Propagate ignore

* Switch to specific ignores

* Add defaults

* Add test repos

* Start adding health tests

* Add golden files

* Fix changelog

* Fix coverage

* Fix breaking

* More fixes

* Revert "Add defaults"

This reverts commit 2bacc71e22beaffaf7fa8b2664f492f8a570788f.

* Remove ignores

* Remove ignores from yamls

* Remove from firehose

* Remove from github

* Remove from repo

* Add changelog

* Move goldens

* Fix glob issue

* Add test data to pubignore

* Switch SDK version

* Move stuff around

* Fix imports

* Give test a name

* Sort license files

* Switch debug messages

* Add debug string

* Add debug

* Add activate global

* Remove debugging

* Fix coverage upload

* fix erros
diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml
index 751bced..87ce1e6 100644
--- a/.github/workflows/health.yaml
+++ b/.github/workflows/health.yaml
@@ -78,8 +78,7 @@
         type: boolean
         required: false
       use-flutter:
-        description: >-
-          Whether to setup Flutter in this workflow.
+        description: Whether to setup Flutter in this workflow.
         default: false
         required: false
         type: boolean
diff --git a/.github/workflows/health_base.yaml b/.github/workflows/health_base.yaml
index 1a4e7da..b8faf86 100644
--- a/.github/workflows/health_base.yaml
+++ b/.github/workflows/health_base.yaml
@@ -45,8 +45,7 @@
         type: boolean
         required: false
       use-flutter:
-        description: >-
-          Whether to setup Flutter in this workflow.
+        description: Whether to setup Flutter in this workflow.
         default: false
         required: false
         type: boolean
@@ -131,7 +130,7 @@
         if: ${{ '$action_state' == 1 }}
 
       - name: Upload coverage to Coveralls
-        if: ${{ inputs.upload_coverage }}
+        if: ${{ inputs.upload_coverage && inputs.check == 'coverage' }}
         uses: coverallsapp/github-action@3dfc5567390f6fa9267c0ee9c251e4c8c3f18949
         with:
           format: lcov
diff --git a/pkgs/firehose/.pubignore b/pkgs/firehose/.pubignore
new file mode 100644
index 0000000..74c3f9f
--- /dev/null
+++ b/pkgs/firehose/.pubignore
@@ -0,0 +1 @@
+test_data/
diff --git a/pkgs/firehose/CHANGELOG.md b/pkgs/firehose/CHANGELOG.md
index 0e106d5..2d232c9 100644
--- a/pkgs/firehose/CHANGELOG.md
+++ b/pkgs/firehose/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.6.0
+
+- Make the health workflow testable with golden tests.
+
 ## 0.5.3
 
 - Allow experiments to be enabled for Dart.
diff --git a/pkgs/firehose/analysis_options.yaml b/pkgs/firehose/analysis_options.yaml
index d978f81..f028315 100644
--- a/pkgs/firehose/analysis_options.yaml
+++ b/pkgs/firehose/analysis_options.yaml
@@ -1 +1,5 @@
 include: package:dart_flutter_team_lints/analysis_options.yaml
+
+analyzer:
+  exclude:
+    - test_data/*
diff --git a/pkgs/firehose/bin/health.dart b/pkgs/firehose/bin/health.dart
index f7f75f9..e1260dc 100644
--- a/pkgs/firehose/bin/health.dart
+++ b/pkgs/firehose/bin/health.dart
@@ -5,6 +5,7 @@
 import 'dart:io';
 
 import 'package:args/args.dart';
+import 'package:firehose/src/github.dart';
 import 'package:firehose/src/health/health.dart';
 
 void main(List<String> arguments) async {
@@ -51,5 +52,6 @@
     failOn,
     coverageWeb,
     experiments,
+    GithubApi(),
   ).healthCheck();
 }
diff --git a/pkgs/firehose/lib/firehose.dart b/pkgs/firehose/lib/firehose.dart
index b1b107b..f5b5c0d 100644
--- a/pkgs/firehose/lib/firehose.dart
+++ b/pkgs/firehose/lib/firehose.dart
@@ -91,7 +91,7 @@
   }
 
   Future<VerificationResults> verify(GithubApi github) async {
-    var repo = Repository();
+    var repo = Repository(directory);
     var packages = repo.locatePackages();
 
     var pub = Pub();
diff --git a/pkgs/firehose/lib/src/github.dart b/pkgs/firehose/lib/src/github.dart
index c37da5b..e3e07b8 100644
--- a/pkgs/firehose/lib/src/github.dart
+++ b/pkgs/firehose/lib/src/github.dart
@@ -19,16 +19,13 @@
 
   static Map<String, String> get _env => Platform.environment;
 
-  /// When true, details of any RPC error are printed to the console.
-  final bool verbose;
-
-  GithubApi({this.verbose = false, RepositorySlug? repoSlug, int? issueNumber})
+  GithubApi({RepositorySlug? repoSlug, int? issueNumber})
       : _repoSlug = repoSlug,
         _issueNumber = issueNumber;
 
   final http.Client _client = DelayedClient(const Duration(milliseconds: 50));
 
-  late GitHub github = githubAuthToken != null
+  late final GitHub _github = githubAuthToken != null
       ? GitHub(
           auth: Authentication.withToken(githubAuthToken),
           client: _client,
@@ -95,7 +92,7 @@
     required String user,
     String? searchTerm,
   }) async {
-    final matchingComment = await github.issues
+    final matchingComment = await _github.issues
         .listCommentsByIssue(repoSlug!, issueNumber!)
         .map<IssueComment?>((comment) => comment)
         .firstWhere(
@@ -110,11 +107,15 @@
     return matchingComment?.id;
   }
 
-  Future<List<GitFile>> listFilesForPR() async => await github.pullRequests
-      .listFiles(repoSlug!, issueNumber!)
-      .map((prFile) =>
-          GitFile(prFile.filename!, FileStatus.fromString(prFile.status!)))
-      .toList();
+  Future<List<GitFile>> listFilesForPR(Directory directory) async =>
+      await _github.pullRequests
+          .listFiles(repoSlug!, issueNumber!)
+          .map((prFile) => GitFile(
+                prFile.filename!,
+                FileStatus.fromString(prFile.status!),
+                directory,
+              ))
+          .toList();
 
   /// Write a notice message to the github log.
   void notice({required String message}) {
@@ -122,26 +123,25 @@
   }
 
   Future<String> pullrequestBody() async {
-    final pullRequest = await github.pullRequests.get(repoSlug!, issueNumber!);
+    final pullRequest = await _github.pullRequests.get(repoSlug!, issueNumber!);
     return pullRequest.body ?? '';
   }
 
-  void close() => github.dispose();
+  void close() => _github.dispose();
 }
 
 class GitFile {
   final String filename;
   final FileStatus status;
+  final Directory directory;
 
   bool isInPackage(Package package) {
-    print('Check if $relativePath is in ${package.directory.path}');
-    return path.isWithin(package.directory.path, relativePath);
+    return path.isWithin(package.directory.path, pathInRepository);
   }
 
-  GitFile(this.filename, this.status);
+  String get pathInRepository => path.join(directory.path, filename);
 
-  String get relativePath =>
-      path.relative(filename, from: Directory.current.path);
+  GitFile(this.filename, this.status, this.directory);
 
   @override
   String toString() => '$filename: $status';
diff --git a/pkgs/firehose/lib/src/health/changelog.dart b/pkgs/firehose/lib/src/health/changelog.dart
index a671433..3474262 100644
--- a/pkgs/firehose/lib/src/health/changelog.dart
+++ b/pkgs/firehose/lib/src/health/changelog.dart
@@ -8,23 +8,27 @@
 
 import '../github.dart';
 import '../repo.dart';
-import '../utils.dart';
 
 Future<Map<Package, List<GitFile>>> packagesWithoutChangelog(
-    GithubApi github) async {
-  final repo = Repository();
+  GithubApi github,
+  Directory directory,
+) async {
+  final repo = Repository(directory);
   final packages = repo.locatePackages();
 
-  final files = await github.listFilesForPR();
+  final files = await github.listFilesForPR(directory);
 
-  var packagesWithoutChangedChangelog =
-      collectPackagesWithoutChangelogChanges(packages, files);
+  var packagesWithoutChangedChangelog = collectPackagesWithoutChangelogChanges(
+    packages,
+    files,
+    directory,
+  );
 
   print('Collecting files without license headers in those packages:');
   var packagesWithChanges = <Package, List<GitFile>>{};
   for (final file in files) {
     for (final package in packagesWithoutChangedChangelog) {
-      if (fileNeedsEntryInChangelog(package, file.relativePath)) {
+      if (fileNeedsEntryInChangelog(package, file.filename, directory)) {
         print(file);
         packagesWithChanges.update(
           package,
@@ -40,21 +44,24 @@
 }
 
 List<Package> collectPackagesWithoutChangelogChanges(
-    List<Package> packages, List<GitFile> files) {
+  List<Package> packages,
+  List<GitFile> files,
+  Directory directory,
+) {
   print('Collecting packages without changed changelogs:');
-  final packagesWithoutChangedChangelog = packages
-      .where((package) => package.changelog.exists)
-      .where((package) => !files
-          .map((e) => e.relativePath)
-          .contains(package.changelog.file.relativePath))
-      .toList();
+  final packagesWithoutChangedChangelog =
+      packages.where((package) => package.changelog.exists).where((package) {
+    return !files
+        .map((e) => e.pathInRepository)
+        .contains(package.changelog.file.path);
+  }).toList();
   print('Done, found ${packagesWithoutChangedChangelog.length} packages.');
   return packagesWithoutChangedChangelog;
 }
 
-bool fileNeedsEntryInChangelog(Package package, String file) {
+bool fileNeedsEntryInChangelog(Package package, String file, Directory d) {
   final directoryPath = package.directory.path;
-  final directory = path.relative(directoryPath, from: Directory.current.path);
+  final directory = path.relative(directoryPath, from: d.path);
   final isInPackage = path.isWithin(directory, file);
   final isInLib = path.isWithin(path.join(directory, 'lib'), file);
   final isInBin = path.isWithin(path.join(directory, 'bin'), file);
diff --git a/pkgs/firehose/lib/src/health/coverage.dart b/pkgs/firehose/lib/src/health/coverage.dart
index 8e617f2..18004d2 100644
--- a/pkgs/firehose/lib/src/health/coverage.dart
+++ b/pkgs/firehose/lib/src/health/coverage.dart
@@ -5,6 +5,7 @@
 
 import 'dart:io';
 
+import 'package:collection/collection.dart';
 import 'package:path/path.dart' as path;
 
 import '../github.dart';
@@ -14,32 +15,31 @@
 
 class Coverage {
   final bool coverageWeb;
+  final Directory directory;
   final List<String> experiments;
 
-  Coverage(this.coverageWeb, this.experiments);
+  Coverage(this.coverageWeb, this.directory, this.experiments);
 
-  Future<CoverageResult> compareCoverages(GithubApi github) async {
-    var files = await github.listFilesForPR();
-    var basePath = '../base_repo/';
+  Future<CoverageResult> compareCoverages(
+      GithubApi github, Directory base) async {
+    var files = await github.listFilesForPR(directory);
 
-    return compareCoveragesFor(files, basePath);
+    return compareCoveragesFor(files, base);
   }
 
-  CoverageResult compareCoveragesFor(List<GitFile> files, String basePath) {
-    var repository = Repository();
+  CoverageResult compareCoveragesFor(List<GitFile> files, Directory base) {
+    var repository = Repository(directory);
     var packages = repository.locatePackages();
-    print('Found packages $packages at ${Directory.current}');
+    print('Found packages $packages at $directory');
 
     var filesOfInterest = files
         .where((file) => path.extension(file.filename) == '.dart')
         .where((file) => file.status != FileStatus.removed)
-        .where((file) => isInSomePackage(packages, file.relativePath))
-        .where((file) => isNotATest(packages, file.relativePath))
+        .where((file) => isInSomePackage(packages, file.filename))
+        .where((file) => isNotATest(packages, file.filename))
         .toList();
     print('The files of interest are $filesOfInterest');
 
-    var base = Directory(basePath);
-
     var baseRepository = Repository(base);
     var basePackages = baseRepository.locatePackages();
     print('Found packages $basePackages at $base');
@@ -47,6 +47,7 @@
     var changedPackages = packages
         .where((package) =>
             filesOfInterest.any((file) => file.isInPackage(package)))
+        .sortedBy((package) => package.name)
         .toList();
 
     print('The packages of interest are $changedPackages');
@@ -59,14 +60,15 @@
           .where((element) => element.name == package.name)
           .firstOrNull;
       final oldCoverages = getCoverage(basePackage);
-      var filePaths = filesOfInterest
+      var filenames = filesOfInterest
           .where((file) => file.isInPackage(package))
-          .map((file) => file.relativePath);
-      for (var filePath in filePaths) {
-        var oldCoverage = oldCoverages[filePath];
-        var newCoverage = newCoverages[filePath];
-        print('Compage coverage for $filePath: $oldCoverage vs $newCoverage');
-        coverageResult[filePath] = Change(
+          .map((file) => file.filename)
+          .sortedBy((filename) => filename);
+      for (var filename in filenames) {
+        var oldCoverage = oldCoverages[filename];
+        var newCoverage = newCoverages[filename];
+        print('Compage coverage for $filename: $oldCoverage vs $newCoverage');
+        coverageResult[filename] = Change(
           oldCoverage: oldCoverage,
           newCoverage: newCoverage,
         );
@@ -76,14 +78,16 @@
   }
 
   bool isNotATest(List<Package> packages, String file) {
-    return packages.every((package) =>
-        !path.isWithin(path.join(package.directory.path, 'test'), file));
+    return packages.every((package) => !path.isWithin(
+        path.join(package.directory.path, 'test'),
+        path.join(directory.path, file)));
   }
 
-  bool isInSomePackage(List<Package> packages, String file) {
-    return packages
-        .any((package) => path.isWithin(package.directory.path, file));
-  }
+  bool isInSomePackage(List<Package> packages, String file) =>
+      packages.any((package) => path.isWithin(
+            package.directory.path,
+            path.join(directory.path, file),
+          ));
 
   Map<String, double> getCoverage(Package? package) {
     if (package != null) {
@@ -103,7 +107,7 @@
           workingDirectory: package.directory.path,
         );
         if (coverageWeb) {
-          print('Get test coverage for web');
+          print('Run tests with coverage for web');
           var resultChrome = Process.runSync(
             'dart',
             [
@@ -119,7 +123,7 @@
           print(resultChrome.stdout);
           print(resultChrome.stderr);
         }
-        print('Get test coverage for vm');
+        print('Run tests with coverage for vm');
         var resultVm = Process.runSync(
           'dart',
           [
@@ -130,8 +134,9 @@
           ],
           workingDirectory: package.directory.path,
         );
-        print(resultVm.stdout);
-        print(resultVm.stderr);
+        print('dart test stdout: ${resultVm.stdout}');
+        print('dart test stderr: ${resultVm.stderr}');
+        print('Compute coverage from runs');
         var resultLcov = Process.runSync(
           'dart',
           [
@@ -149,8 +154,8 @@
           ],
           workingDirectory: package.directory.path,
         );
-        print(resultLcov.stdout);
-        print(resultLcov.stderr);
+        print('dart coverage stdout: ${resultLcov.stdout}');
+        print('dart coverage stderr: ${resultLcov.stderr}');
         return parseLCOV(
           path.join(package.directory.path, 'coverage/lcov.info'),
           relativeTo: package.repository.baseDirectory.path,
diff --git a/pkgs/firehose/lib/src/health/health.dart b/pkgs/firehose/lib/src/health/health.dart
index 5b06e96..94ab8d0 100644
--- a/pkgs/firehose/lib/src/health/health.dart
+++ b/pkgs/firehose/lib/src/health/health.dart
@@ -44,6 +44,8 @@
 class Health {
   final Directory directory;
 
+  final String commentPath;
+
   Health(
     this.directory,
     this.check,
@@ -51,13 +53,23 @@
     this.failOn,
     this.coverageweb,
     this.experiments,
-  );
-  final github = GithubApi();
+    this.github, {
+    Directory? base,
+    String? comment,
+  })  : baseDirectory = base ?? Directory('../base_repo'),
+        commentPath = comment ??
+            path.join(
+              directory.path,
+              'output',
+              'comment.md',
+            );
+  final GithubApi github;
 
   final String check;
   final List<String> warnOn;
   final List<String> failOn;
   final bool coverageweb;
+  final Directory baseDirectory;
   final List<String> experiments;
 
   Future<void> healthCheck() async {
@@ -127,35 +139,34 @@
   }
 
   Future<HealthCheckResult> breakingCheck() async {
-    final filesInPR = await github.listFilesForPR();
+    final filesInPR = await github.listFilesForPR(directory);
     final changeForPackage = <Package, BreakingChange>{};
-    final baseDirectory = Directory('../base_repo');
     for (var package in packagesContaining(filesInPR)) {
-      var currentPath =
-          path.relative(package.directory.path, from: Directory.current.path);
-      var basePackage = path.relative(
-        path.join(baseDirectory.absolute.path, currentPath),
-        from: currentPath,
-      );
-      print('Look for changes in $currentPath with base $basePackage');
+      print('Look for changes in $package with base $baseDirectory');
+      var relativePath =
+          path.relative(package.directory.path, from: directory.path);
+      var baseRelativePath = path.relative(
+          path.join(baseDirectory.path, relativePath),
+          from: directory.path);
+      var tempDirectory = Directory.systemTemp..createSync();
+      var reportPath = path.join(tempDirectory.path, 'report.json');
       var runApiTool = Process.runSync(
         'dart',
         [
           ...['pub', 'global', 'run'],
           'dart_apitool:main',
           'diff',
-          ...['--old', basePackage],
-          ...['--new', '.'],
+          ...['--old', baseRelativePath],
+          ...['--new', relativePath],
           ...['--report-format', 'json'],
-          ...['--report-file-path', 'report.json'],
+          ...['--report-file-path', reportPath],
         ],
-        workingDirectory: currentPath,
+        workingDirectory: directory.path,
       );
       print(runApiTool.stderr);
       print(runApiTool.stdout);
 
-      final reportFile = File(path.join(currentPath, 'report.json'));
-      var fullReportString = reportFile.readAsStringSync();
+      var fullReportString = File(reportPath).readAsStringSync();
       var decoded = jsonDecode(fullReportString) as Map<String, dynamic>;
       var report = decoded['report'] as Map<String, dynamic>;
 
@@ -200,11 +211,12 @@
   }
 
   Future<HealthCheckResult> licenseCheck() async {
-    var files = await github.listFilesForPR();
-    var allFilePaths = await getFilesWithoutLicenses(Directory.current);
+    var files = await github.listFilesForPR(directory);
+    var allFilePaths = await getFilesWithoutLicenses(directory);
 
-    var groupedPaths = allFilePaths
-        .groupListsBy((path) => files.any((f) => f.relativePath == path));
+    var groupedPaths = allFilePaths.groupListsBy((filePath) {
+      return files.any((f) => f.filename == filePath);
+    });
 
     var unchangedFilesPaths = groupedPaths[false] ?? [];
     var unchangedMarkdown = '''
@@ -243,12 +255,12 @@
   }
 
   Future<HealthCheckResult> changelogCheck() async {
-    var filePaths = await packagesWithoutChangelog(github);
+    var filePaths = await packagesWithoutChangelog(github, directory);
 
     final markdownResult = '''
 | Package | Changed Files |
 | :--- | :--- |
-${filePaths.entries.map((e) => '| package:${e.key.name} | ${e.value.map((e) => e.relativePath).join('<br />')} |').join('\n')}
+${filePaths.entries.map((e) => '| package:${e.key.name} | ${e.value.map((e) => e.filename).join('<br />')} |').join('\n')}
 
 Changes to files need to be [accounted for](https://github.com/dart-lang/ecosystem/wiki/Changelog) in their respective changelogs.
 ''';
@@ -264,14 +276,14 @@
     final dns = 'DO_NOT${'_'}SUBMIT';
 
     final body = await github.pullrequestBody();
-    final files = await github.listFilesForPR();
+    final files = await github.listFilesForPR(directory);
     print('Checking for $dns strings: $files');
     final filesWithDNS = files
         .where((file) =>
             ![FileStatus.removed, FileStatus.unchanged].contains(file.status))
-        .where((file) => File(file.relativePath).existsSync())
-        .where(
-            (file) => File(file.relativePath).readAsStringSync().contains(dns))
+        .where((file) => File(file.pathInRepository).existsSync())
+        .where((file) =>
+            File(file.pathInRepository).readAsStringSync().contains(dns))
         .toList();
     print('Found files with $dns: $filesWithDNS');
 
@@ -294,8 +306,8 @@
   }
 
   Future<HealthCheckResult> coverageCheck() async {
-    var coverage =
-        await Coverage(coverageweb, experiments).compareCoverages(github);
+    var coverage = await Coverage(coverageweb, directory, experiments)
+        .compareCoverages(github, baseDirectory);
 
     var markdownResult = '''
 | File | Coverage |
@@ -339,7 +351,7 @@
 
     github.appendStepSummary(markdownSummary);
 
-    var commentFile = File('./output/comment.md');
+    var commentFile = File(commentPath);
     print('Saving comment markdown to file ${commentFile.path}');
     await commentFile.create(recursive: true);
     await commentFile.writeAsString(markdownSummary);
@@ -351,13 +363,12 @@
 
   List<Package> packagesContaining(List<GitFile> filesInPR) {
     var files = filesInPR.where((element) => element.status.isRelevant);
-    final repo = Repository();
-    return repo.locatePackages().where((package) {
-      var relativePackageDirectory =
-          path.relative(package.directory.path, from: Directory.current.path);
-      return files.any(
-          (file) => path.isWithin(relativePackageDirectory, file.relativePath));
-    }).toList();
+    final repo = Repository(directory);
+    return repo
+        .locatePackages()
+        .where((package) => files.any((file) =>
+            path.isWithin(package.directory.path, file.pathInRepository)))
+        .toList();
   }
 }
 
diff --git a/pkgs/firehose/lib/src/health/license.dart b/pkgs/firehose/lib/src/health/license.dart
index 0a2a657..b7bec93 100644
--- a/pkgs/firehose/lib/src/health/license.dart
+++ b/pkgs/firehose/lib/src/health/license.dart
@@ -4,6 +4,7 @@
 
 import 'dart:io';
 
+import 'package:collection/collection.dart';
 import 'package:path/path.dart' as path;
 
 final license = '''
@@ -22,13 +23,13 @@
         var fileContents = File(file.path).readAsStringSync();
         var fileContainsCopyright = fileContents.contains('// Copyright (c)');
         if (!fileContainsCopyright) {
-          var relativePath =
-              path.relative(file.path, from: Directory.current.path);
+          var relativePath = path.relative(file.path, from: repositoryDir.path);
           print(relativePath);
           return relativePath;
         }
       })
       .whereType<String>()
+      .sortedBy((fileName) => fileName)
       .toList();
   print('''
 Done, found ${filesWithoutLicenses.length} files without license headers''');
diff --git a/pkgs/firehose/lib/src/utils.dart b/pkgs/firehose/lib/src/utils.dart
index 9032083..bcc5d7c 100644
--- a/pkgs/firehose/lib/src/utils.dart
+++ b/pkgs/firehose/lib/src/utils.dart
@@ -5,8 +5,6 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:path/path.dart' as path;
-
 /// Execute the given CLI command asynchronously, streaming stdout and stderr to
 /// the console.
 ///
@@ -113,11 +111,6 @@
   }
 }
 
-extension FileExtension on File {
-  String get relativePath =>
-      path.relative(this.path, from: Directory.current.path);
-}
-
 enum Severity {
   success(':heavy_check_mark:'),
   info(':heavy_check_mark:'),
diff --git a/pkgs/firehose/pubspec.yaml b/pkgs/firehose/pubspec.yaml
index de9ff9b..6c8d36c 100644
--- a/pkgs/firehose/pubspec.yaml
+++ b/pkgs/firehose/pubspec.yaml
@@ -1,6 +1,6 @@
 name: firehose
 description: A tool to automate publishing of Pub packages from GitHub actions.
-version: 0.5.3
+version: 0.6.0
 repository: https://github.com/dart-lang/ecosystem/tree/main/pkgs/firehose
 
 environment:
diff --git a/pkgs/firehose/test/coverage_test.dart b/pkgs/firehose/test/coverage_test.dart
index 2cfbeaa..51922a0 100644
--- a/pkgs/firehose/test/coverage_test.dart
+++ b/pkgs/firehose/test/coverage_test.dart
@@ -33,8 +33,8 @@
   });
   test('Compare coverage', () async {
     var coverages = FakeHealth().compareCoveragesFor(
-      [GitFile('testfile.dart', FileStatus.modified)],
-      'base_path_does_not_exist',
+      [GitFile('testfile.dart', FileStatus.modified, Directory.current)],
+      Directory('base_path_does_not_exist'),
     );
 
     expect(coverages.coveragePerFile,
@@ -43,7 +43,7 @@
 }
 
 class FakeHealth extends Coverage {
-  FakeHealth() : super(true, []);
+  FakeHealth() : super(true, Directory.current, []);
 
   @override
   Map<String, double> getCoverage(Package? package) {
diff --git a/pkgs/firehose/test/github_test.dart b/pkgs/firehose/test/github_test.dart
index ef47b54..1094144 100644
--- a/pkgs/firehose/test/github_test.dart
+++ b/pkgs/firehose/test/github_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:io';
+
 import 'package:firehose/src/github.dart';
 import 'package:github/github.dart';
 import 'package:test/test.dart';
@@ -19,9 +21,13 @@
             'Bumps [actions/labeler](https://github.com/actions/labeler) from 4.0.4 to 4.3.0.\n'));
   });
   test('Listing files for PR', () async {
-    var files = await github.listFilesForPR();
+    var files = await github.listFilesForPR(Directory.current);
     expect(files, [
-      GitFile('.github/workflows/pull_request_label.yml', FileStatus.modified),
+      GitFile(
+        '.github/workflows/pull_request_label.yml',
+        FileStatus.modified,
+        Directory.current,
+      ),
     ]);
   });
   test('Find comment', () async {
diff --git a/pkgs/firehose/test/health_test.dart b/pkgs/firehose/test/health_test.dart
new file mode 100644
index 0000000..a0e7b13
--- /dev/null
+++ b/pkgs/firehose/test/health_test.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:firehose/src/github.dart';
+import 'package:firehose/src/health/health.dart';
+import 'package:github/src/common/model/repos.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+  test('Check health workflow against golden files', () async {
+    final directory = Directory(p.join('test_data', 'test_repo'));
+    var fakeGithubApi = FakeGithubApi(prLabels: [], files: [
+      GitFile(
+        'pkgs/package1/bin/package1.dart',
+        FileStatus.modified,
+        directory,
+      ),
+      GitFile(
+        'pkgs/package2/lib/anotherLib.dart',
+        FileStatus.added,
+        directory,
+      ),
+    ]);
+    await Process.run('dart', ['pub', 'global', 'activate', 'dart_apitool']);
+    await Process.run('dart', ['pub', 'global', 'activate', 'coverage']);
+    for (var check in checkTypes) {
+      var comment = await checkFor(check, fakeGithubApi, directory);
+      var goldenFile = File(p.join('test_data', 'golden', 'comment_$check.md'));
+      var goldenComment = goldenFile.readAsStringSync();
+      if (Platform.environment.containsKey('RESET_GOLDEN')) {
+        goldenFile.writeAsStringSync(comment);
+      } else {
+        expect(comment, goldenComment);
+      }
+    }
+  }, timeout: const Timeout(Duration(minutes: 2)));
+}
+
+Future<String> checkFor(
+  String check,
+  FakeGithubApi fakeGithubApi,
+  Directory directory,
+) async {
+  final comment = p.join(Directory.systemTemp.path, 'comment_$check.md');
+  await Health(
+    directory,
+    check,
+    [],
+    [],
+    false,
+    [],
+    fakeGithubApi,
+    base: Directory(p.join('test_data', 'base_test_repo')),
+    comment: comment,
+  ).healthCheck();
+  return await File(comment).readAsString();
+}
+
+class FakeGithubApi implements GithubApi {
+  final List<GitFile> files;
+
+  FakeGithubApi({
+    required this.prLabels,
+    required this.files,
+  });
+
+  @override
+  String? get actor => throw UnimplementedError();
+
+  @override
+  void appendStepSummary(String markdownSummary) {}
+
+  @override
+  String? get baseRef => throw UnimplementedError();
+
+  @override
+  void close() {}
+
+  @override
+  Future<int?> findCommentId({required String user, String? searchTerm}) {
+    throw UnimplementedError();
+  }
+
+  @override
+  String? get githubAuthToken => throw UnimplementedError();
+
+  @override
+  bool get inGithubContext => throw UnimplementedError();
+
+  @override
+  int? get issueNumber => 1;
+
+  @override
+  Future<List<GitFile>> listFilesForPR(Directory directory) async {
+    return files;
+  }
+
+  @override
+  void notice({required String message}) {}
+
+  @override
+  final List<String> prLabels;
+
+  @override
+  Future<String> pullrequestBody() async => 'Test body';
+
+  @override
+  String? get refName => throw UnimplementedError();
+
+  @override
+  RepositorySlug? get repoSlug => RepositorySlug('test_owner', 'test_repo');
+
+  @override
+  String? get sha => 'test_sha';
+}
diff --git a/pkgs/firehose/test/license_test.dart b/pkgs/firehose/test/license_test.dart
index f269d73..b8af03f 100644
--- a/pkgs/firehose/test/license_test.dart
+++ b/pkgs/firehose/test/license_test.dart
@@ -8,6 +8,7 @@
 import 'dart:io';
 
 import 'package:firehose/src/health/license.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 void main() {
@@ -20,9 +21,10 @@
   });
 
   test('Check for licenses', () async {
-    var filesWithoutLicenses =
-        await getFilesWithoutLicenses(Directory('test/'));
-    expect(filesWithoutLicenses, [fileWithoutLicense.path]);
+    var directory = Directory('test/');
+    var filesWithoutLicenses = await getFilesWithoutLicenses(directory);
+    expect(filesWithoutLicenses,
+        [path.relative(fileWithoutLicense.path, from: directory.path)]);
   });
 
   tearDown(() async {
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/.gitignore b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/.gitignore
new file mode 100644
index 0000000..3a85790
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/.gitignore
@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/CHANGELOG.md b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/README.md b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/README.md
new file mode 100644
index 0000000..3816eca
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/README.md
@@ -0,0 +1,2 @@
+A sample command-line application with an entrypoint in `bin/`, library code
+in `lib/`, and example unit test in `test/`.
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/analysis_options.yaml b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/bin/package1.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/bin/package1.dart
new file mode 100644
index 0000000..a8525dd
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/bin/package1.dart
@@ -0,0 +1,5 @@
+import 'package:package1/package1.dart' as package1;
+
+void main(List<String> arguments) {
+  print('Hello world: ${package1.calculate()}!');
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/lib/package1.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/lib/package1.dart
new file mode 100644
index 0000000..f64ad72
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/lib/package1.dart
@@ -0,0 +1,3 @@
+int calculate() {
+  return 6 * 7;
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/pubspec.yaml b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/pubspec.yaml
new file mode 100644
index 0000000..fe566c2
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/pubspec.yaml
@@ -0,0 +1,15 @@
+name: package1
+description: A sample command-line application.
+version: 1.0.0
+# repository: https://github.com/my_org/my_repo
+
+environment:
+  sdk: ^3.0.0
+
+# Add regular dependencies here.
+dependencies:
+  # path: ^1.8.0
+
+dev_dependencies:
+  lints: ^3.0.0
+  test: ^1.24.0
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package1/test/package1_test.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/test/package1_test.dart
new file mode 100644
index 0000000..5d8a815
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package1/test/package1_test.dart
@@ -0,0 +1,8 @@
+import 'package:package1/package1.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('calculate', () {
+    expect(calculate(), 42);
+  });
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/.gitignore b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/.gitignore
new file mode 100644
index 0000000..3a85790
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/.gitignore
@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/CHANGELOG.md b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/README.md b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/README.md
new file mode 100644
index 0000000..3816eca
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/README.md
@@ -0,0 +1,2 @@
+A sample command-line application with an entrypoint in `bin/`, library code
+in `lib/`, and example unit test in `test/`.
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/analysis_options.yaml b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/bin/package2.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/bin/package2.dart
new file mode 100644
index 0000000..dcc9c52
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/bin/package2.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:package2/package2.dart' as package2;
+
+void main(List<String> arguments) {
+  print('Hello world: ${package2.calculate()}!');
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/lib/package2.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/lib/package2.dart
new file mode 100644
index 0000000..f64ad72
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/lib/package2.dart
@@ -0,0 +1,3 @@
+int calculate() {
+  return 6 * 7;
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/pubspec.yaml b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/pubspec.yaml
new file mode 100644
index 0000000..4f148c6
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/pubspec.yaml
@@ -0,0 +1,15 @@
+name: package2
+description: A sample command-line application.
+version: 1.0.0
+# repository: https://github.com/my_org/my_repo
+
+environment:
+  sdk: ^3.0.0
+
+# Add regular dependencies here.
+dependencies:
+  # path: ^1.8.0
+
+dev_dependencies:
+  lints: ^3.0.0
+  test: ^1.24.0
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package2/test/package2_test.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/test/package2_test.dart
new file mode 100644
index 0000000..0b0e31b
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package2/test/package2_test.dart
@@ -0,0 +1,8 @@
+import 'package:package2/package2.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('calculate', () {
+    expect(calculate(), 42);
+  });
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/.gitignore b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/.gitignore
new file mode 100644
index 0000000..3a85790
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/.gitignore
@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/CHANGELOG.md b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/README.md b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/README.md
new file mode 100644
index 0000000..3816eca
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/README.md
@@ -0,0 +1,2 @@
+A sample command-line application with an entrypoint in `bin/`, library code
+in `lib/`, and example unit test in `test/`.
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/analysis_options.yaml b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/bin/package3.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/bin/package3.dart
new file mode 100644
index 0000000..b6ba11d
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/bin/package3.dart
@@ -0,0 +1,5 @@
+import 'package:package3/package3.dart' as package3;
+
+void main(List<String> arguments) {
+  print('Hello world: ${package3.calculate()}!');
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/lib/package3.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/lib/package3.dart
new file mode 100644
index 0000000..f64ad72
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/lib/package3.dart
@@ -0,0 +1,3 @@
+int calculate() {
+  return 6 * 7;
+}
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/pubspec.yaml b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/pubspec.yaml
new file mode 100644
index 0000000..2454f72
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/pubspec.yaml
@@ -0,0 +1,15 @@
+name: package3
+description: A sample command-line application.
+version: 1.0.0
+# repository: https://github.com/my_org/my_repo
+
+environment:
+  sdk: ^3.0.0
+
+# Add regular dependencies here.
+dependencies:
+  # path: ^1.8.0
+
+dev_dependencies:
+  lints: ^3.0.0
+  test: ^1.24.0
diff --git a/pkgs/firehose/test_data/base_test_repo/pkgs/package3/test/package3_test.dart b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/test/package3_test.dart
new file mode 100644
index 0000000..a2f5a1b
--- /dev/null
+++ b/pkgs/firehose/test_data/base_test_repo/pkgs/package3/test/package3_test.dart
@@ -0,0 +1,8 @@
+import 'package:package3/package3.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('calculate', () {
+    expect(calculate(), 42);
+  });
+}
diff --git a/pkgs/firehose/test_data/golden/comment_breaking.md b/pkgs/firehose/test_data/golden/comment_breaking.md
new file mode 100644
index 0000000..422549b
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_breaking.md
@@ -0,0 +1,16 @@
+### Breaking changes :warning:
+
+<details open>
+<summary>
+Details
+</summary>
+
+| Package | Change | Current Version | New Version | Needed Version | Looking good? |
+| :--- | :--- | ---: | ---: | ---: | ---: |
+|package1|None|1.0.0|1.0.0|1.0.0|:heavy_check_mark:|
+|package2|Non-Breaking|1.0.0|1.0.0|**1.1.0** <br> Got "1.0.0" expected >= "1.1.0" (non-breaking changes)|:warning:|
+
+
+This check can be disabled by tagging the PR with `skip-breaking-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_changelog.md b/pkgs/firehose/test_data/golden/comment_changelog.md
new file mode 100644
index 0000000..dfbadb2
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_changelog.md
@@ -0,0 +1,18 @@
+### Changelog Entry :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+| Package | Changed Files |
+| :--- | :--- |
+| package:package1 | pkgs/package1/bin/package1.dart |
+| package:package2 | pkgs/package2/lib/anotherLib.dart |
+
+Changes to files need to be [accounted for](https://github.com/dart-lang/ecosystem/wiki/Changelog) in their respective changelogs.
+
+
+This check can be disabled by tagging the PR with `skip-changelog-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_coverage.md b/pkgs/firehose/test_data/golden/comment_coverage.md
new file mode 100644
index 0000000..c594cbf
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_coverage.md
@@ -0,0 +1,18 @@
+### Coverage :warning:
+
+<details open>
+<summary>
+Details
+</summary>
+
+| File | Coverage |
+| :--- | :--- |
+|pkgs/package1/bin/package1.dart| :broken_heart: Not covered |
+|pkgs/package2/lib/anotherLib.dart| :green_heart: 100 % |
+
+This check for [test coverage](https://github.com/dart-lang/ecosystem/wiki/Test-Coverage) is informational (issues shown here will not fail the PR).
+
+
+This check can be disabled by tagging the PR with `skip-coverage-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_do-not-submit.md b/pkgs/firehose/test_data/golden/comment_do-not-submit.md
new file mode 100644
index 0000000..8daf42d
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_do-not-submit.md
@@ -0,0 +1,17 @@
+### Do Not Submit :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+Body contains `DO_NOT_SUBMIT`: false
+
+| Files with `DO_NOT_SUBMIT` |
+| :--- |
+|pkgs/package1/bin/package1.dart|
+
+
+This check can be disabled by tagging the PR with `skip-do-not-submit-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_license.md b/pkgs/firehose/test_data/golden/comment_license.md
new file mode 100644
index 0000000..a53ef1c
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_license.md
@@ -0,0 +1,42 @@
+### License Headers :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+```
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+```
+
+| Files |
+| :--- |
+|pkgs/package1/bin/package1.dart|
+|pkgs/package2/lib/anotherLib.dart|
+
+All source files should start with a [license header](https://github.com/dart-lang/ecosystem/wiki/License-Header).
+
+<details>
+<summary>
+Unrelated files missing license headers
+</summary>
+
+| Files |
+| :--- |
+|pkgs/package1/lib/package1.dart|
+|pkgs/package1/test/package1_test.dart|
+|pkgs/package2/lib/package2.dart|
+|pkgs/package2/test/package2_test.dart|
+|pkgs/package3/bin/package3.dart|
+|pkgs/package3/lib/package3.dart|
+|pkgs/package3/test/package3_test.dart|
+</details>
+
+
+
+
+This check can be disabled by tagging the PR with `skip-license-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/golden/comment_version.md b/pkgs/firehose/test_data/golden/comment_version.md
new file mode 100644
index 0000000..c03092a
--- /dev/null
+++ b/pkgs/firehose/test_data/golden/comment_version.md
@@ -0,0 +1,19 @@
+### Package publish validation :exclamation:
+
+<details open>
+<summary>
+Details
+</summary>
+
+| Package | Version | Status |
+| :--- | ---: | :--- |
+| package:package1 | 1.0.0 | (error) pub publish dry-run failed; add the `publish-ignore-warnings` label to ignore |
+| package:package2 | 1.0.0 | (error) pub publish dry-run failed; add the `publish-ignore-warnings` label to ignore |
+| package:package3 | 1.0.0 | (error) pub publish dry-run failed; add the `publish-ignore-warnings` label to ignore |
+
+Documentation at https://github.com/dart-lang/ecosystem/wiki/Publishing-automation.
+    
+
+This check can be disabled by tagging the PR with `skip-version-check`
+</details>
+
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/.gitignore b/pkgs/firehose/test_data/test_repo/pkgs/package1/.gitignore
new file mode 100644
index 0000000..3a85790
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/.gitignore
@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/CHANGELOG.md b/pkgs/firehose/test_data/test_repo/pkgs/package1/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/README.md b/pkgs/firehose/test_data/test_repo/pkgs/package1/README.md
new file mode 100644
index 0000000..3816eca
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/README.md
@@ -0,0 +1,2 @@
+A sample command-line application with an entrypoint in `bin/`, library code
+in `lib/`, and example unit test in `test/`.
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/analysis_options.yaml b/pkgs/firehose/test_data/test_repo/pkgs/package1/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/bin/package1.dart b/pkgs/firehose/test_data/test_repo/pkgs/package1/bin/package1.dart
new file mode 100644
index 0000000..9fb0943
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/bin/package1.dart
@@ -0,0 +1,6 @@
+import 'package:package1/package1.dart' as package1;
+
+void main(List<String> arguments) {
+  // Add a comment DO_NOT_SUBMIT
+  print('Hello world: ${package1.calculate()}!');
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/lib/package1.dart b/pkgs/firehose/test_data/test_repo/pkgs/package1/lib/package1.dart
new file mode 100644
index 0000000..f64ad72
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/lib/package1.dart
@@ -0,0 +1,3 @@
+int calculate() {
+  return 6 * 7;
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/pubspec.yaml b/pkgs/firehose/test_data/test_repo/pkgs/package1/pubspec.yaml
new file mode 100644
index 0000000..fe566c2
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/pubspec.yaml
@@ -0,0 +1,15 @@
+name: package1
+description: A sample command-line application.
+version: 1.0.0
+# repository: https://github.com/my_org/my_repo
+
+environment:
+  sdk: ^3.0.0
+
+# Add regular dependencies here.
+dependencies:
+  # path: ^1.8.0
+
+dev_dependencies:
+  lints: ^3.0.0
+  test: ^1.24.0
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package1/test/package1_test.dart b/pkgs/firehose/test_data/test_repo/pkgs/package1/test/package1_test.dart
new file mode 100644
index 0000000..5d8a815
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package1/test/package1_test.dart
@@ -0,0 +1,8 @@
+import 'package:package1/package1.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('calculate', () {
+    expect(calculate(), 42);
+  });
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/.gitignore b/pkgs/firehose/test_data/test_repo/pkgs/package2/.gitignore
new file mode 100644
index 0000000..3a85790
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/.gitignore
@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/CHANGELOG.md b/pkgs/firehose/test_data/test_repo/pkgs/package2/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/README.md b/pkgs/firehose/test_data/test_repo/pkgs/package2/README.md
new file mode 100644
index 0000000..3816eca
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/README.md
@@ -0,0 +1,2 @@
+A sample command-line application with an entrypoint in `bin/`, library code
+in `lib/`, and example unit test in `test/`.
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/analysis_options.yaml b/pkgs/firehose/test_data/test_repo/pkgs/package2/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/bin/package2.dart b/pkgs/firehose/test_data/test_repo/pkgs/package2/bin/package2.dart
new file mode 100644
index 0000000..dcc9c52
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/bin/package2.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:package2/package2.dart' as package2;
+
+void main(List<String> arguments) {
+  print('Hello world: ${package2.calculate()}!');
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/lib/anotherLib.dart b/pkgs/firehose/test_data/test_repo/pkgs/package2/lib/anotherLib.dart
new file mode 100644
index 0000000..af52fa1
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/lib/anotherLib.dart
@@ -0,0 +1,3 @@
+int calculateUnused() {
+  return 6 * 7;
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/lib/package2.dart b/pkgs/firehose/test_data/test_repo/pkgs/package2/lib/package2.dart
new file mode 100644
index 0000000..f64ad72
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/lib/package2.dart
@@ -0,0 +1,3 @@
+int calculate() {
+  return 6 * 7;
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/pubspec.yaml b/pkgs/firehose/test_data/test_repo/pkgs/package2/pubspec.yaml
new file mode 100644
index 0000000..4f148c6
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/pubspec.yaml
@@ -0,0 +1,15 @@
+name: package2
+description: A sample command-line application.
+version: 1.0.0
+# repository: https://github.com/my_org/my_repo
+
+environment:
+  sdk: ^3.0.0
+
+# Add regular dependencies here.
+dependencies:
+  # path: ^1.8.0
+
+dev_dependencies:
+  lints: ^3.0.0
+  test: ^1.24.0
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package2/test/package2_test.dart b/pkgs/firehose/test_data/test_repo/pkgs/package2/test/package2_test.dart
new file mode 100644
index 0000000..cfe57f0
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package2/test/package2_test.dart
@@ -0,0 +1,12 @@
+import 'package:package2/anotherLib.dart' as anotherLib;
+import 'package:package2/package2.dart' as p2;
+import 'package:test/test.dart';
+
+void main() {
+  test('calculate', () {
+    expect(p2.calculate(), 42);
+  });
+  test('calculate anotherlib', () {
+    expect(anotherLib.calculateUnused(), 42);
+  });
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/.gitignore b/pkgs/firehose/test_data/test_repo/pkgs/package3/.gitignore
new file mode 100644
index 0000000..3a85790
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/.gitignore
@@ -0,0 +1,3 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/CHANGELOG.md b/pkgs/firehose/test_data/test_repo/pkgs/package3/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/README.md b/pkgs/firehose/test_data/test_repo/pkgs/package3/README.md
new file mode 100644
index 0000000..3816eca
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/README.md
@@ -0,0 +1,2 @@
+A sample command-line application with an entrypoint in `bin/`, library code
+in `lib/`, and example unit test in `test/`.
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/analysis_options.yaml b/pkgs/firehose/test_data/test_repo/pkgs/package3/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/bin/package3.dart b/pkgs/firehose/test_data/test_repo/pkgs/package3/bin/package3.dart
new file mode 100644
index 0000000..b6ba11d
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/bin/package3.dart
@@ -0,0 +1,5 @@
+import 'package:package3/package3.dart' as package3;
+
+void main(List<String> arguments) {
+  print('Hello world: ${package3.calculate()}!');
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/lib/package3.dart b/pkgs/firehose/test_data/test_repo/pkgs/package3/lib/package3.dart
new file mode 100644
index 0000000..f64ad72
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/lib/package3.dart
@@ -0,0 +1,3 @@
+int calculate() {
+  return 6 * 7;
+}
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/pubspec.yaml b/pkgs/firehose/test_data/test_repo/pkgs/package3/pubspec.yaml
new file mode 100644
index 0000000..2454f72
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/pubspec.yaml
@@ -0,0 +1,15 @@
+name: package3
+description: A sample command-line application.
+version: 1.0.0
+# repository: https://github.com/my_org/my_repo
+
+environment:
+  sdk: ^3.0.0
+
+# Add regular dependencies here.
+dependencies:
+  # path: ^1.8.0
+
+dev_dependencies:
+  lints: ^3.0.0
+  test: ^1.24.0
diff --git a/pkgs/firehose/test_data/test_repo/pkgs/package3/test/package3_test.dart b/pkgs/firehose/test_data/test_repo/pkgs/package3/test/package3_test.dart
new file mode 100644
index 0000000..a2f5a1b
--- /dev/null
+++ b/pkgs/firehose/test_data/test_repo/pkgs/package3/test/package3_test.dart
@@ -0,0 +1,8 @@
+import 'package:package3/package3.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('calculate', () {
+    expect(calculate(), 42);
+  });
+}