// Copyright (c) 2020, 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.

@TestOn('vm')
library;

import 'dart:async';
import 'dart:io';

import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_core/src/util/exit_codes.dart' as exit_codes;
import 'package:test_descriptor/test_descriptor.dart' as d;

import '../io.dart';
import 'json_reporter_utils.dart';

void main() {
  setUpAll(precompileTestExecutable);

  test('runs successful tests with a stdout reporter and file reporter', () {
    return _expectReports(
      '''
      test('success 1', () {});
      test('success 2', () {});
      test('success 3', () {});
    ''',
      '''
      +0: loading test.dart
      +0: success 1
      +1: success 2
      +2: success 3
      +3: All tests passed!''',
      [
        [
          suiteJson(0),
          testStartJson(1, 'loading test.dart', groupIDs: []),
          testDoneJson(1, hidden: true),
        ],
        [
          groupJson(2, testCount: 3),
          testStartJson(3, 'success 1', line: 6, column: 7),
          testDoneJson(3),
          testStartJson(4, 'success 2', line: 7, column: 7),
          testDoneJson(4),
          testStartJson(5, 'success 3', line: 8, column: 7),
          testDoneJson(5),
        ],
      ],
      doneJson(),
    );
  });

  test('runs failing tests with a stdout reporter and file reporter', () {
    return _expectReports(
      '''
      test('failure 1', () => throw new TestFailure('oh no'));
      test('failure 2', () => throw new TestFailure('oh no'));
      test('failure 3', () => throw new TestFailure('oh no'));
    ''',
      '''
      +0: loading test.dart
      +0: failure 1
      +0 -1: failure 1 [E]
        oh no
        test.dart 6:31  main.<fn>

      +0 -1: failure 2
      +0 -2: failure 2 [E]
        oh no
        test.dart 7:31  main.<fn>

      +0 -2: failure 3
      +0 -3: failure 3 [E]
        oh no
        test.dart 8:31  main.<fn>

      +0 -3: Some tests failed.''',
      [
        [
          suiteJson(0),
          testStartJson(1, 'loading test.dart', groupIDs: []),
          testDoneJson(1, hidden: true),
        ],
        [
          groupJson(2, testCount: 3),
          testStartJson(3, 'failure 1', line: 6, column: 7),
          errorJson(3, 'oh no', isFailure: true),
          testDoneJson(3, result: 'failure'),
          testStartJson(4, 'failure 2', line: 7, column: 7),
          errorJson(4, 'oh no', isFailure: true),
          testDoneJson(4, result: 'failure'),
          testStartJson(5, 'failure 3', line: 8, column: 7),
          errorJson(5, 'oh no', isFailure: true),
          testDoneJson(5, result: 'failure'),
        ],
      ],
      doneJson(success: false),
    );
  });

  group('reports an error if --file-reporter', () {
    test('is not in the form <reporter>:<filepath>', () async {
      var test = await runTest(['--file-reporter=json']);
      expect(
        test.stderr,
        emits(contains('option must be in the form <reporter>:<filepath>')),
      );
      await test.shouldExit(exit_codes.usage);
    });

    test('targets a non-existent reporter', () async {
      var test = await runTest(['--file-reporter=nope:output.txt']);
      expect(
        test.stderr,
        emits(contains('"nope" is not a supported reporter')),
      );
      await test.shouldExit(exit_codes.usage);
    });
  });
}

Future<void> _expectReports(
  String tests,
  String stdoutExpected,
  List<List<Object /*Map|Matcher*/>> jsonFileExpected,
  Map<Object, Object> jsonFileDone, {
  List<String> args = const [],
}) async {
  await d.file('test.dart', '''
    import 'dart:async';

    import 'package:test/test.dart';

    void main() {
$tests
    }
  ''').create();

  var test = await runTest(
    ['test.dart', '--chain-stack-traces', ...args],
    // Write to a file within a dir that doesn't yet exist to verify that the
    // file is created recursively.
    fileReporter: 'json:reports/tests.json',
  );
  await test.shouldExit();

  // ---- stdout reporter verification ----
  var stdoutLines = await test.stdoutStream().toList();

  // Remove excess trailing whitespace and trim off timestamps.
  var actual = stdoutLines
      .map((line) {
        if (line.startsWith('  ') || line.isEmpty) return line.trimRight();
        return line.trim().replaceFirst(RegExp('^[0-9]{2}:[0-9]{2} '), '');
      })
      .join('\n');

  // Un-indent the expected string.
  var indentation = stdoutExpected.indexOf(RegExp('[^ ]'));
  stdoutExpected = stdoutExpected
      .split('\n')
      .map((line) {
        if (line.isEmpty) return line;
        return line.substring(indentation);
      })
      .join('\n');

  expect(actual, equals(stdoutExpected));

  // ---- file reporter verification ----
  var fileOutputLines =
      File(p.join(d.sandbox, 'reports', 'tests.json')).readAsLinesSync();
  await expectJsonReport(
    fileOutputLines,
    test.pid,
    jsonFileExpected,
    jsonFileDone,
  );
}
