finalizeCucumberResults function

Future<bool> finalizeCucumberResults({
  1. required String path,
  2. required String runnerJsonPath,
  3. required int testExitCode,
})

Publishes the final CucumberResultsReporter report at path, using the actual result of a completed test process. This is what the dart run bdd_framework:finalize_cucumber_results command calls. See CucumberResultsReporter for how to run both steps.

The test process must have used --file-reporter=json:<runnerJsonPath>. Call this only after it exits, passing its unchanged testExitCode.

Returns true for a confirmed success, and false for a confirmed test failure. Missing, interrupted, inconsistent or unsupported results throw, and leave no final report. The global result covers every test selected by the command, including tests that are not BDDs.

What is checked:

  • The partial at path.partial has a random run ID, which the reporter also prints into the runner's JSON output. The run ID and the output path must match in both files.
  • The runner's JSON output must have exactly one start event, and end with a conclusive done event that agrees with testExitCode.
  • The test cases and steps in the partial must reference each other correctly.
  • Only the public JSON protocol 0.1.x and the normal exit codes 0 and 1 are supported. Other exit codes, cancellations, missing events and contradictions leave the result incomplete, instead of guessing.
  • The partial and the runner's JSON output are read up to 64 MiB each, and the runner's JSON output up to 1,000,000 events.

The partial is consumed, even when validation fails, so finalizing again requires running the tests again.

This prevents accidentally mixing runs, or reusing the same partial. It does not authenticate the local files or the exit code your script passes, and can't stop someone from restoring copies of both matching files. Keep these inputs trusted, pass the actual exit code unchanged, and don't reuse directories. If the test process fails before the reporter runs, a new directory also prevents an older report from looking current.

Use one reporter per output path, and a new private directory per command. Don't run two finalizers on the same path at the same time. Links in the paths are rejected, but the parent directories must not be changed while this runs.

Implementation

Future<bool> finalizeCucumberResults({
  required String path,
  required String runnerJsonPath,
  required int testExitCode,
}) async {
  final outputUri = _canonicalUri(path);
  final partialPath = '$path.partial';
  if ({
    outputUri,
    _canonicalUri(partialPath),
  }.contains(_canonicalUri(runnerJsonPath))) {
    throw ArgumentError('Runner JSON must have a separate file path.');
  }
  if (FileSystemEntity.typeSync(runnerJsonPath, followLinks: false) ==
      FileSystemEntityType.link) {
    throw FileSystemException(
      'Runner JSON must not be a link.',
      runnerJsonPath,
    );
  }

  // Also reject filesystem aliases (for example Windows short names or hard
  // links) before invalidating a file which may be the supplied runner input.
  for (final candidate in [path, partialPath]) {
    if (FileSystemEntity.typeSync(candidate, followLinks: false) ==
            FileSystemEntityType.file &&
        FileSystemEntity.typeSync(runnerJsonPath, followLinks: false) ==
            FileSystemEntityType.file &&
        await FileSystemEntity.identical(candidate, runnerJsonPath)) {
      throw ArgumentError('Runner JSON aliases a Messages file.');
    }
  }

  await invalidateMessagesFile(path);
  final partial = _PartialReport(await consumeMessagesFile(partialPath));
  final runnerJson = await readMessagesFile(runnerJsonPath);
  final success = readMessagesRunnerResult(
    runnerJson,
    runId: partial.runId,
    outputUri: outputUri,
    testExitCode: testExitCode,
  );
  if (success && !partial.bodiesPassed) {
    throw const FormatException(
      'Runner success contradicts an incomplete or failing final BDD attempt.',
    );
  }
  final finished = messages.Envelope(
    testRunFinished: messages.TestRunFinished(
      success: success,
      timestamp: messages.dateTimeToTimestamp(DateTime.now()),
      testRunStartedId: partial.runId,
      message:
          'Final result of the complete test command (exit $testExitCode). '
          'Step details describe BDD bodies; setup, teardown and timeout '
          'failures may occur outside those steps.',
    ),
  );
  await writeMessagesFile(path, () sync* {
    yield* partial.lines;
    yield messages.envelopeToJsonString(finished);
  });
  return success;
}