// 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_core/src/util/io.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;

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

void main() {
  setUpAll(precompileTestExecutable);

  group('presets', () {
    test("don't do anything by default", () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
              },
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

      await (await runTest(['test.dart'])).shouldExit(0);
    });

    test('can be selected on the command line', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
              },
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

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

    test('multiple presets can be selected', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
                'bar': {
                  'paths': ['test.dart'],
                },
              },
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

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

    test('the latter preset takes precedence', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
                'bar': {'timeout': '30s'},
              },
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

      await (await runTest(['-P', 'foo,bar', 'test.dart'])).shouldExit(0);

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

    test('a preset takes precedence over the base configuration', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
              },
              'timeout': '30s',
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

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

      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '30s'},
              },
              'timeout': '00s',
            }),
          )
          .create();

      await (await runTest(['-P', 'foo', 'test.dart'])).shouldExit(0);
    });

    test('a nested preset is activated', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'tags': {
                'foo': {
                  'presets': {
                    '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));
        }
      ''').create();

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

      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '30s'},
              },
              'timeout': '00s',
            }),
          )
          .create();

      await (await runTest(['-P', 'foo', 'test.dart'])).shouldExit(0);
    });
  });

  group('add_presets', () {
    test('selects a preset', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
              },
              'add_presets': ['foo'],
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

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

    test('applies presets in selection order', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
                'bar': {'timeout': '30s'},
              },
              'add_presets': ['foo', 'bar'],
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

      await (await runTest(['test.dart'])).shouldExit(0);

      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {'timeout': '0s'},
                'bar': {'timeout': '30s'},
              },
              'add_presets': ['bar', 'foo'],
            }),
          )
          .create();

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

    test('allows preset inheritance via add_presets', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {
                  'add_presets': ['bar'],
                },
                'bar': {'timeout': '0s'},
              },
            }),
          )
          .create();

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

        import 'package:test/test.dart';

        void main() {
          test("test", () => Future.delayed(Duration.zero));
        }
      ''').create();

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

    test('allows circular preset inheritance via add_presets', () async {
      await d
          .file(
            'dart_test.yaml',
            jsonEncode({
              'presets': {
                'foo': {
                  'add_presets': ['bar'],
                },
                'bar': {
                  'add_presets': ['foo'],
                },
              },
            }),
          )
          .create();

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

        import 'package:test/test.dart';

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

      await (await runTest(['-P', 'foo', 'test.dart'])).shouldExit(0);
    });
  });

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

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

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

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

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

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

      test('rejects an invalid preset configuration', () async {
        await d
            .file(
              'dart_test.yaml',
              jsonEncode({
                'presets': {
                  '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 in a non-runner context', () async {
        await d
            .file(
              'dart_test.yaml',
              jsonEncode({
                'tags': {
                  'foo': {
                    'presets': {
                      'bar': {'filename': '*_blorp.dart'},
                    },
                  },
                },
              }),
            )
            .create();

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

      test('fails if an undefined preset is passed', () async {
        var test = await runTest(['-P', 'foo']);
        expect(test.stderr, emitsThrough(contains('Undefined preset "foo".')));
        await test.shouldExit(exit_codes.usage);
      });

      test('fails if an undefined preset is added', () async {
        await d
            .file(
              'dart_test.yaml',
              jsonEncode({
                'add_presets': ['foo', 'bar'],
              }),
            )
            .create();

        var test = await runTest([]);
        expect(
          test.stderr,
          emitsThrough(contains('Undefined presets "foo" and "bar".')),
        );
        await test.shouldExit(exit_codes.usage);
      });

      test(
        'fails if an undefined preset is added in a nested context',
        () async {
          await d
              .file(
                'dart_test.yaml',
                jsonEncode({
                  'on_os': {
                    currentOS.identifier: {
                      'add_presets': ['bar'],
                    },
                  },
                }),
              )
              .create();

          var test = await runTest([]);
          expect(
            test.stderr,
            emitsThrough(contains('Undefined preset "bar".')),
          );
          await test.shouldExit(exit_codes.usage);
        },
      );
    });

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

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

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

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

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

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