// Copyright (c) 2016, 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:convert';

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';

void main() {
  setUpAll(precompileTestExecutable);

  test('adds the specified tags', () async {
    await d
        .file(
            'dart_test.yaml',
            jsonEncode({
              'add_tags': ['foo', 'bar']
            }))
        .create();

    await d.file('test.dart', '''
      import 'package:test/test.dart';

      void main() {
        test("test", () {});
      }
    ''').create();

    var test = await runTest(['--exclude-tag', 'foo', 'test.dart']);
    expect(test.stdout, emitsThrough(contains('No tests ran.')));
    await test.shouldExit(79);

    test = await runTest(['--exclude-tag', 'bar', 'test.dart']);
    expect(test.stdout, emitsThrough(contains('No tests ran.')));
    await test.shouldExit(79);

    test = await runTest(['test.dart']);
    expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
    await test.shouldExit(0);
  });

  group('tags', () {
    test("doesn't warn for tags that exist in the configuration", () async {
      await d
          .file(
              'dart_test.yaml',
              jsonEncode({
                'tags': {'foo': null}
              }))
          .create();

      await d.file('test.dart', '''
        import 'package:test/test.dart';

        void main() {
          test("test", () {});
        }
      ''').create();

      var test = await runTest(['test.dart']);
      expect(test.stdout, neverEmits(contains('Warning: Tags were used')));
      await test.shouldExit(0);
    });

    test('applies tag-specific configuration only to matching tests', () async {
      await d
          .file(
              'dart_test.yaml',
              jsonEncode({
                'tags': {
                  'foo': {'timeout': '0s'}
                }
              }))
          .create();

      await d.file('test.dart', '''
        import 'dart:async';

        import 'package:test/test.dart';

        void main() {
          test("test 1", () => Future.delayed(Duration.zero), tags: ['foo']);
          test("test 2", () => Future.delayed(Duration.zero));
        }
      ''').create();

      var test = await runTest(['test.dart']);
      expect(test.stdout,
          containsInOrder(['-1: test 1 [E]', '+1 -1: Some tests failed.']));
      await test.shouldExit(1);
    });

    test('supports tag selectors', () async {
      await d
          .file(
              'dart_test.yaml',
              jsonEncode({
                'tags': {
                  'foo && bar': {'timeout': '0s'}
                }
              }))
          .create();

      await d.file('test.dart', '''
        import 'dart:async';

        import 'package:test/test.dart';

        void main() {
          test("test 1", () => Future.delayed(Duration.zero), tags: ['foo']);
          test("test 2", () => Future.delayed(Duration.zero), tags: ['bar']);
          test("test 3", () => Future.delayed(Duration.zero),
              tags: ['foo', 'bar']);
        }
      ''').create();

      var test = await runTest(['test.dart']);
      expect(test.stdout,
          containsInOrder(['+2 -1: test 3 [E]', '+2 -1: Some tests failed.']));
      await test.shouldExit(1);
    });

    test('allows tag inheritance via add_tags', () async {
      await d
          .file(
              'dart_test.yaml',
              jsonEncode({
                'tags': {
                  'foo': null,
                  'bar': {
                    'add_tags': ['foo']
                  }
                }
              }))
          .create();

      await d.file('test.dart', '''
        import 'package:test/test.dart';

        void main() {
          test("test 1", () {}, tags: ['bar']);
          test("test 2", () {});
        }
      ''').create();

      var test = await runTest(['test.dart', '--tags', 'foo']);
      expect(test.stdout, emitsThrough(contains('+1: All tests passed!')));
      await test.shouldExit(0);
    });

    // Regression test for #503.
    test('skips tests whose tags are marked as skip', () async {
      await d
          .file(
              'dart_test.yaml',
              jsonEncode({
                'tags': {
                  'foo': {'skip': 'some reason'}
                }
              }))
          .create();

      await d.file('test.dart', '''
        import 'dart:async';

        import 'package:test/test.dart';

        void main() {
          test("test 1", () => throw 'bad', tags: ['foo']);
        }
      ''').create();

      var test = await runTest(['test.dart']);
      expect(
          test.stdout, containsInOrder(['some reason', 'All tests skipped.']));
      await test.shouldExit(0);
    });
  });

  group('include_tags and exclude_tags', () {
    test('only runs tests with the included tags', () async {
      await d
          .file('dart_test.yaml', jsonEncode({'include_tags': 'foo && bar'}))
          .create();

      await d.file('test.dart', '''
        import 'package:test/test.dart';

        void main() {
          test("zip", () {}, tags: "foo");
          test("zap", () {}, tags: "bar");
          test("zop", () {}, tags: ["foo", "bar"]);
        }
      ''').create();

      var test = await runTest(['test.dart']);
      expect(
          test.stdout, containsInOrder(['+0: zop', '+1: All tests passed!']));
      await test.shouldExit(0);
    });

    test("doesn't run tests with the excluded tags", () async {
      await d
          .file('dart_test.yaml', jsonEncode({'exclude_tags': 'foo && bar'}))
          .create();

      await d.file('test.dart', '''
        import 'package:test/test.dart';

        void main() {
          test("zip", () {}, tags: "foo");
          test("zap", () {}, tags: "bar");
          test("zop", () {}, tags: ["foo", "bar"]);
        }
      ''').create();

      var test = await runTest(['test.dart']);
      expect(test.stdout,
          containsInOrder(['+0: zip', '+1: zap', '+2: All tests passed!']));
      await test.shouldExit(0);
    });
  });

  group('errors', () {
    group('tags', () {
      test('rejects an invalid tag type', () async {
        await d.file('dart_test.yaml', '{"tags": {12: null}}').create();

        var test = await runTest([]);
        expect(
            test.stderr, containsInOrder(['tags key must be a string', '^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects an invalid tag selector', () async {
        await d
            .file(
                'dart_test.yaml',
                jsonEncode({
                  'tags': {'foo bar': null}
                }))
            .create();

        var test = await runTest([]);
        expect(
            test.stderr,
            containsInOrder(
                ['Invalid tags key: Expected end of input.', '^^^^^^^^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects an invalid tag map', () async {
        await d.file('dart_test.yaml', jsonEncode({'tags': 12})).create();

        var test = await runTest([]);
        expect(test.stderr, containsInOrder(['tags must be a map', '^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects an invalid tag configuration', () async {
        await d
            .file(
                'dart_test.yaml',
                jsonEncode({
                  'tags': {
                    'foo': {'timeout': '12p'}
                  }
                }))
            .create();

        var test = await runTest([]);
        expect(test.stderr,
            containsInOrder(['Invalid timeout: expected unit', '^^^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects runner configuration', () async {
        await d
            .file(
                'dart_test.yaml',
                jsonEncode({
                  'tags': {
                    'foo': {'filename': '*_blorp.dart'}
                  }
                }))
            .create();

        var test = await runTest([]);
        expect(test.stderr,
            containsInOrder(["filename isn't supported here.", '^^^^^^^^^^']));
        await test.shouldExit(exit_codes.data);
      });
    });

    group('add_tags', () {
      test('rejects an invalid list type', () async {
        await d
            .file('dart_test.yaml', jsonEncode({'add_tags': 'foo'}))
            .create();

        var test = await runTest(['test.dart']);
        expect(
            test.stderr, containsInOrder(['add_tags must be a list', '^^^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects an invalid tag type', () async {
        await d
            .file(
                'dart_test.yaml',
                jsonEncode({
                  'add_tags': [12]
                }))
            .create();

        var test = await runTest(['test.dart']);
        expect(
            test.stderr, containsInOrder(['Tag name must be a string', '^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects an invalid tag name', () async {
        await d
            .file(
                'dart_test.yaml',
                jsonEncode({
                  'add_tags': ['foo bar']
                }))
            .create();

        var test = await runTest(['test.dart']);
        expect(
            test.stderr,
            containsInOrder([
              'Tag name must be an (optionally hyphenated) Dart identifier.',
              '^^^^^^^^^'
            ]));
        await test.shouldExit(exit_codes.data);
      });
    });

    group('include_tags', () {
      test('rejects an invalid type', () async {
        await d
            .file('dart_test.yaml', jsonEncode({'include_tags': 12}))
            .create();

        var test = await runTest(['test.dart']);
        expect(test.stderr,
            containsInOrder(['include_tags must be a string', '^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects an invalid selector', () async {
        await d
            .file('dart_test.yaml', jsonEncode({'include_tags': 'foo bar'}))
            .create();

        var test = await runTest([]);
        expect(
            test.stderr,
            containsInOrder(
                ['Invalid include_tags: Expected end of input.', '^^^^^^^^^']));
        await test.shouldExit(exit_codes.data);
      });
    });

    group('exclude_tags', () {
      test('rejects an invalid type', () async {
        await d
            .file('dart_test.yaml', jsonEncode({'exclude_tags': 12}))
            .create();

        var test = await runTest(['test.dart']);
        expect(test.stderr,
            containsInOrder(['exclude_tags must be a string', '^^']));
        await test.shouldExit(exit_codes.data);
      });

      test('rejects an invalid selector', () async {
        await d
            .file('dart_test.yaml', jsonEncode({'exclude_tags': 'foo bar'}))
            .create();

        var test = await runTest([]);
        expect(
            test.stderr,
            containsInOrder(
                ['Invalid exclude_tags: Expected end of input.', '^^^^^^^^^']));
        await test.shouldExit(exit_codes.data);
      });
    });
  });
}
