Support --version in pub workspace (#2538)

Closes #2535

Add a check for the `workspace_ref.json` file in place of the
`pubspec.lock` file that exists for single package pub solutions.

When the workspace ref exists, follow it and read the
`package_graph.json` file from the workspace root.
diff --git a/pkgs/test/test/runner/json_reporter_utils.dart b/pkgs/test/test/runner/json_reporter_utils.dart
index 7416c0a..eec2808 100644
--- a/pkgs/test/test/runner/json_reporter_utils.dart
+++ b/pkgs/test/test/runner/json_reporter_utils.dart
@@ -6,7 +6,6 @@
 
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
-import 'package:test_core/src/runner/version.dart';
 import 'package:test_descriptor/test_descriptor.dart' as d;
 
 /// Asserts that the outputs from running tests with a JSON reporter match the
@@ -41,7 +40,8 @@
   final start = {
     'type': 'start',
     'protocolVersion': '0.1.1',
-    'runnerVersion': testVersion,
+    // Runner version cannot be read in the synthetic package
+    'runnerVersion': null,
     'pid': testPid,
   };
   expect(decoded.first, equals(start));
diff --git a/pkgs/test_core/CHANGELOG.md b/pkgs/test_core/CHANGELOG.md
index 885647d..27324b6 100644
--- a/pkgs/test_core/CHANGELOG.md
+++ b/pkgs/test_core/CHANGELOG.md
@@ -7,6 +7,7 @@
 * Suppress skip reason messages in the compact and failures-only reporters.
 * Fix default coverage filter when running in a workspace package. Default
   filter now includes all the workspace's package.
+* Add support for reading test package version within pub workspaces.
 
 ## 0.6.14
 
diff --git a/pkgs/test_core/lib/src/runner/version.dart b/pkgs/test_core/lib/src/runner/version.dart
index aff6791..05054ce 100644
--- a/pkgs/test_core/lib/src/runner/version.dart
+++ b/pkgs/test_core/lib/src/runner/version.dart
@@ -2,60 +2,92 @@
 // 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:convert';
 import 'dart:io';
 
+import 'package:collection/collection.dart';
+import 'package:path/path.dart' as p;
 import 'package:yaml/yaml.dart';
 
 /// The version number of the test runner, or `null` if it couldn't be loaded.
 ///
 /// This is a semantic version, optionally followed by a space and additional
 /// data about its source.
-final String? testVersion =
-    (() {
-      dynamic lockfile;
-      try {
-        lockfile = loadYaml(File('pubspec.lock').readAsStringSync());
-      } on FormatException catch (_) {
-        return null;
-      } on IOException catch (_) {
-        return null;
-      }
+final String? testVersion = _readWorkspaceRef() ?? _readPubspecLock();
 
-      if (lockfile is! Map) return null;
-      var packages = lockfile['packages'];
-      if (packages is! Map) return null;
-      var package = packages['test'];
-      if (package is! Map) return null;
+String? _readWorkspaceRef() {
+  try {
+    final pubDirectory = p.join('.dart_tool', 'pub');
+    final workspaceRefFile = File(p.join(pubDirectory, 'workspace_ref.json'));
+    if (!workspaceRefFile.existsSync()) return null;
+    final workspaceRef = jsonDecode(workspaceRefFile.readAsStringSync());
+    if (workspaceRef is! Map) return null;
+    final relativeRoot = workspaceRef['workspaceRoot'];
+    if (relativeRoot is! String) return null;
+    final packageGraphPath = p.normalize(
+      p.join(pubDirectory, relativeRoot, '.dart_tool', 'package_graph.json'),
+    );
+    final packageGraph = jsonDecode(File(packageGraphPath).readAsStringSync());
+    if (packageGraph is! Map) return null;
+    final packages = packageGraph['packages'];
+    if (packages is! List) return null;
+    final testPackage = packages.firstWhereOrNull(
+      (p) => p is Map && p['name'] == 'test',
+    );
+    if (testPackage == null) return null;
+    return (testPackage as Map)['version'] as String;
+  } on FormatException {
+    return null;
+  } on IOException {
+    return null;
+  }
+}
 
-      var source = package['source'];
-      if (source is! String) return null;
+String? _readPubspecLock() {
+  dynamic lockfile;
+  try {
+    lockfile = loadYaml(File('pubspec.lock').readAsStringSync());
+  } on FormatException {
+    return null;
+  } on IOException {
+    return null;
+  }
 
-      switch (source) {
-        case 'hosted':
-          var version = package['version'];
-          return (version is String) ? version : null;
+  if (lockfile is! Map) return null;
+  var packages = lockfile['packages'];
+  if (packages is! Map) return null;
+  var package = packages['test'];
+  if (package is! Map) return null;
 
-        case 'git':
-          var version = package['version'];
-          if (version is! String) return null;
-          var description = package['description'];
-          if (description is! Map) return null;
-          var ref = description['resolved-ref'];
-          if (ref is! String) return null;
+  var source = package['source'];
+  if (source is! String) return null;
 
-          return '$version (${ref.substring(0, 7)})';
+  switch (source) {
+    case 'hosted':
+      var version = package['version'];
+      return (version is String) ? version : null;
 
-        case 'path':
-          var version = package['version'];
-          if (version is! String) return null;
-          var description = package['description'];
-          if (description is! Map) return null;
-          var path = description['path'];
-          if (path is! String) return null;
+    case 'git':
+      var version = package['version'];
+      if (version is! String) return null;
+      var description = package['description'];
+      if (description is! Map) return null;
+      var ref = description['resolved-ref'];
+      if (ref is! String) return null;
 
-          return '$version (from $path)';
+      return '$version (${ref.substring(0, 7)})';
 
-        default:
-          return null;
-      }
-    })();
+    case 'path':
+      var version = package['version'];
+      if (version is! String) return null;
+      var description = package['description'];
+      if (description is! Map) return null;
+      var path = description['path'];
+      if (path is! String) return null;
+
+      return '$version (from $path)';
+
+    default:
+      return null;
+  }
+}