| // Copyright (c) 2023, 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. |
| |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:cli_config/cli_config.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'helpers.dart'; |
| |
| void main() { |
| test('optionalStringList', () { |
| const path1 = 'path/in/cli_arguments/'; |
| const path2 = 'path/in/cli_arguments_2/'; |
| const path3 = 'path/in/environment/'; |
| const path4 = 'path/in/environment_2/'; |
| const path5 = 'path/in/config_file/'; |
| const path6 = 'path/in/config_file_2/'; |
| final config = Config.fromConfigFileContents( |
| commandLineDefines: [ |
| 'build.out_dir=$path1', |
| 'build.out_dir=$path2', |
| ], |
| environment: { |
| 'BUILD__OUT_DIR': '$path3:$path4', |
| }, |
| fileContents: jsonEncode( |
| { |
| 'build': { |
| 'out_dir': [ |
| path5, |
| path6, |
| ], |
| } |
| }, |
| ), |
| ); |
| |
| { |
| final result = config.optionalStringList( |
| 'build.out_dir', |
| combineAllConfigs: true, |
| splitEnvironmentPattern: ':', |
| ); |
| expect(result, [path1, path2, path3, path4, path5, path6]); |
| } |
| |
| { |
| final result = config.optionalStringList( |
| 'build.out_dir', |
| combineAllConfigs: false, |
| splitEnvironmentPattern: ':', |
| ); |
| expect(result, [path1, path2]); |
| } |
| }); |
| |
| test('optionalString cli precedence', () { |
| const path1 = 'path/in/cli_arguments/'; |
| const path2 = 'path/in/environment/'; |
| const path3 = 'path/in/config_file/'; |
| final config = Config.fromConfigFileContents( |
| commandLineDefines: [ |
| 'build.out_dir=$path1', |
| ], |
| environment: { |
| 'BUILD__OUT_DIR': path2, |
| }, |
| fileContents: jsonEncode( |
| { |
| 'build': { |
| 'out_dir': path3, |
| } |
| }, |
| ), |
| ); |
| |
| final result = config.optionalString( |
| 'build.out_dir', |
| ); |
| expect(result, path1); |
| }); |
| |
| test('optionalString environment precedence', () { |
| const path2 = 'path/in/environment/'; |
| const path3 = 'path/in/config_file/'; |
| final config = Config.fromConfigFileContents( |
| commandLineDefines: [], |
| environment: { |
| 'BUILD__OUT_DIR': path2, |
| }, |
| fileContents: jsonEncode( |
| { |
| 'build': { |
| 'out_dir': path3, |
| } |
| }, |
| ), |
| ); |
| |
| final result = config.optionalString( |
| 'build.out_dir', |
| ); |
| expect(result, path2); |
| }); |
| |
| test('optionalString config file', () { |
| const path3 = 'path/in/config_file/'; |
| final config = Config.fromConfigFileContents( |
| commandLineDefines: [], |
| environment: {}, |
| fileContents: jsonEncode( |
| { |
| 'build': { |
| 'out_dir': path3, |
| } |
| }, |
| ), |
| ); |
| |
| final result = config.optionalString( |
| 'build.out_dir', |
| ); |
| expect(result, path3); |
| }); |
| |
| test('optionalBool define', () { |
| final config = Config( |
| commandLineDefines: ['my_bool=true'], |
| ); |
| |
| expect(config.optionalBool('my_bool'), true); |
| }); |
| |
| test('optionalBool environment', () { |
| final config = Config( |
| environment: { |
| 'MY_BOOL': 'true', |
| }, |
| ); |
| |
| expect(config.optionalBool('my_bool'), true); |
| }); |
| |
| test('optionalBool file', () { |
| final config = Config.fromConfigFileContents( |
| fileContents: jsonEncode( |
| {'my_bool': true}, |
| ), |
| ); |
| |
| expect(config.optionalBool('my_bool'), true); |
| }); |
| |
| test('Read file and parse CLI args', () async { |
| final temp = await Directory.systemTemp.createTemp(); |
| final configFile = File.fromUri(temp.uri.resolve('config.yaml')); |
| await configFile.writeAsString(jsonEncode( |
| { |
| 'build': { |
| 'out_dir': 'path/in/config_file/', |
| } |
| }, |
| )); |
| final config = await Config.fromArgs( |
| args: [ |
| '--config', |
| configFile.path, |
| '-Dbuild.out_dir=path/in/cli_arguments/', |
| ], |
| environment: { |
| 'BUILD__OUT_DIR': 'path/in/environment', |
| }, |
| ); |
| |
| final result = config.optionalString('build.out_dir'); |
| expect(result, 'path/in/cli_arguments/'); |
| }); |
| |
| test('Resolve config file path relative to config file', () async { |
| final temp = await Directory.systemTemp.createTemp(); |
| final tempUri = temp.uri; |
| final configUri = tempUri.resolve('config.yaml'); |
| final configFile = File.fromUri(configUri); |
| const relativePath = 'path/in/config_file/'; |
| final resolvedPath = configUri.resolve(relativePath); |
| |
| await configFile.writeAsString(jsonEncode( |
| { |
| 'build': { |
| 'out_dir': relativePath, |
| } |
| }, |
| )); |
| final config = await Config.fromArgs( |
| args: [ |
| '--config', |
| configFile.path, |
| ], |
| ); |
| |
| final result = config.optionalPath('build.out_dir'); |
| expect(result!.path, resolvedPath.path); |
| }); |
| |
| test('provide pre-parsed config', () { |
| const path3 = 'path/in/config_file/'; |
| final config = Config( |
| commandLineDefines: [], |
| environment: {}, |
| fileParsed: { |
| 'build': { |
| 'out_dir': path3, |
| } |
| }, |
| ); |
| |
| final result = config.optionalString('build.out_dir'); |
| expect(result, path3); |
| }); |
| |
| test('path exists', () async { |
| await inTempDir((tempUri) async { |
| final tempFileUri = tempUri.resolve('file.ext'); |
| await File.fromUri(tempFileUri).create(); |
| final nonExistUri = tempUri.resolve('foo.ext'); |
| final config = Config( |
| commandLineDefines: [], |
| environment: {}, |
| fileParsed: { |
| 'build': { |
| 'out_dir': tempUri.toFilePath(), |
| 'file': tempFileUri.toFilePath(), |
| 'non_exist': nonExistUri.toFilePath(), |
| } |
| }, |
| ); |
| |
| final result = config.optionalPath('build.out_dir', mustExist: true); |
| expect(result, tempUri); |
| final result2 = config.optionalPath('build.file', mustExist: true); |
| expect(result2, tempFileUri); |
| expect( |
| () => config.optionalPath('build.non_exist', mustExist: true), |
| throwsFormatException, |
| ); |
| }); |
| }); |
| |
| test('wrong CLI key format', () { |
| expect( |
| () => Config(commandLineDefines: ['CAPITALIZED=value']), |
| throwsFormatException, |
| ); |
| }); |
| |
| test('CLI two values when expecting one', () { |
| final config = Config(commandLineDefines: ['key=value', 'key=value2']); |
| expect( |
| () => config.string('key'), |
| throwsFormatException, |
| ); |
| }); |
| |
| test('CLI split optionalStringList', () { |
| final config = Config(commandLineDefines: ['key=value;value2']); |
| final value = config.optionalStringList('key', splitCliPattern: ';'); |
| expect(value, ['value', 'value2']); |
| }); |
| |
| test('CLI path', () { |
| final uri = Uri.file('some/path.ext'); |
| final config = Config(commandLineDefines: ['key=${uri.path}']); |
| final value = config.optionalPath('key'); |
| expect(value, uri); |
| }); |
| |
| test('CLI path list', () { |
| final uri = Uri.file('some/path.ext'); |
| final uri2 = Uri.file('some/directory/'); |
| final config = Config(commandLineDefines: ['key=${uri.path}:${uri2.path}']); |
| final value = config.optionalPathList('key', splitCliPattern: ':'); |
| expect(value, [uri, uri2]); |
| }); |
| |
| test('toString', () { |
| final config = Config( |
| commandLineDefines: ['key=foo'], |
| environment: {'key': 'bar'}, |
| fileParsed: {'key': 'baz'}, |
| ); |
| config.toString(); |
| }); |
| |
| test('Missing nonullable throws FormatException', () { |
| final config = Config.fromConfigFileContents(); |
| expect(() => config.bool('key'), throwsFormatException); |
| expect(() => config.string('key'), throwsFormatException); |
| expect(() => config.path('key'), throwsFormatException); |
| }); |
| |
| test('string not validValue throws FormatException', () { |
| final config = Config(environment: {'foo': 'bar'}); |
| expect( |
| () => config.string('foo', validValues: ['not_bar']), |
| throwsFormatException, |
| ); |
| }); |
| |
| test('optionalString validValues', () { |
| final config = Config(); |
| expect(config.optionalString('foo', validValues: ['bar']), isNull); |
| }); |
| |
| test('valueOf file source', () { |
| final config = Config(fileParsed: { |
| 'key': {'some': 'map'} |
| }); |
| final value = config.valueOf<Map<dynamic, dynamic>>('key'); |
| expect(value, {'some': 'map'}); |
| }); |
| |
| test('valueOf command line source', () { |
| final config = Config(commandLineDefines: [ |
| 'string_key=value', |
| 'bool_key=true', |
| 'string_list_key=value1', |
| 'string_list_key=value2', |
| ]); |
| expect(config.valueOf<String>('string_key'), 'value'); |
| expect(config.valueOf<bool>('bool_key'), true); |
| expect( |
| config.valueOf<List<String>>('string_list_key'), |
| ['value1', 'value2'], |
| ); |
| }); |
| |
| test('environment split optionalStringList', () { |
| final config = Config(environment: {'key': 'value;value2'}); |
| final value = |
| config.optionalStringList('key', splitEnvironmentPattern: ';'); |
| expect(value, ['value', 'value2']); |
| }); |
| |
| test('environment non split optionalStringList', () { |
| final config = Config(environment: {'key': 'value'}); |
| final value = config.optionalStringList('key'); |
| expect(value, ['value']); |
| }); |
| |
| test('environment path', () { |
| final uri = Uri.file('some/path.ext'); |
| final config = Config(environment: {'key': uri.path}); |
| final value = config.optionalPath('key'); |
| expect(value, uri); |
| }); |
| |
| test('environment path list', () { |
| final uri = Uri.file('some/path.ext'); |
| final uri2 = Uri.file('some/directory/'); |
| final config = Config(environment: {'key': '${uri.path}:${uri2.path}'}); |
| final value = config.optionalPathList('key', splitEnvironmentPattern: ':'); |
| expect(value, [uri, uri2]); |
| }); |
| |
| test('Unexpected config file contents', () { |
| expect(() => Config.fromConfigFileContents(fileContents: 'asdf'), |
| throwsFormatException); |
| expect(() => Config.fromConfigFileContents(fileContents: "['asdf']"), |
| throwsFormatException); |
| expect( |
| () => Config.fromConfigFileContents( |
| fileContents: ''' |
| WRONGKEY: |
| 1 |
| ''' |
| .trim()), |
| throwsFormatException, |
| ); |
| expect( |
| () => Config.fromConfigFileContents( |
| fileContents: ''' |
| 1: 'asdf' |
| ''' |
| .trim()), |
| throwsFormatException, |
| ); |
| }); |
| |
| test('file config try to access object as wrong type', () { |
| final config = Config.fromConfigFileContents(fileContents: '''foo: |
| bar: |
| true |
| '''); |
| expect(config.bool('foo.bar'), true); |
| expect(() => config.bool('foo.bar.baz'), throwsFormatException); |
| expect(() => config.string('foo.bar'), throwsFormatException); |
| }); |
| |
| test('file config path list unresolved', () { |
| final uri = Uri.file('some/path.ext'); |
| final uri2 = Uri.file('some/directory/'); |
| final config = Config(fileParsed: { |
| 'key': [uri.path, uri2.path] |
| }); |
| final value = config.optionalPathList('key', resolveUri: false); |
| expect(value, [uri, uri2]); |
| }); |
| |
| test('file config path list resolved', () { |
| final configUri = Uri.file('path/to/config.json'); |
| final uri = Uri.file('some/path.ext'); |
| final uri2 = Uri.file('some/directory/'); |
| final config = Config( |
| fileSourceUri: configUri, |
| fileParsed: { |
| 'key': [uri.path, uri2.path] |
| }, |
| ); |
| final value = config.optionalPathList('key', resolveUri: true); |
| expect(value, [configUri.resolveUri(uri), configUri.resolveUri(uri2)]); |
| }); |
| |
| test('resolveUri in working directory', () { |
| final systemTemp = Directory.systemTemp.uri; |
| final tempUri = systemTemp.resolve('x/y/z/'); |
| |
| final relativePath = Uri.file('a/b/c/d.ext'); |
| final absolutePath = tempUri.resolveUri(relativePath); |
| final config = Config( |
| commandLineDefines: ['path=${relativePath.path}'], |
| workingDirectory: tempUri, |
| ); |
| |
| expect(config.optionalPath('path', mustExist: false, resolveUri: true), |
| absolutePath); |
| }); |
| |
| test('ints', () { |
| final config = Config( |
| commandLineDefines: ['cl=1', 'not_parsable=asdf'], |
| environment: { |
| 'env': '2', |
| 'not_parsable2': 'asfd', |
| }, |
| fileParsed: {'file': 3}, |
| ); |
| |
| expect(config.int('cl'), 1); |
| expect(config.optionalInt('env'), 2); |
| expect(config.optionalInt('file'), 3); |
| expect(config.optionalInt('nothing'), null); |
| expect(() => config.optionalInt('not_parsable'), throwsFormatException); |
| expect(() => config.optionalInt('not_parsable2'), throwsFormatException); |
| }); |
| |
| test('doubles', () { |
| final config = Config( |
| commandLineDefines: ['cl=1.1', 'not_parsable=asdf'], |
| environment: { |
| 'env': '2.2', |
| 'not_parsable2': 'asfd', |
| }, |
| fileParsed: {'file': 3.3}, |
| ); |
| |
| expect(config.double('cl'), 1.1); |
| expect(config.optionalDouble('env'), 2.2); |
| expect(config.optionalDouble('file'), 3.3); |
| expect(config.optionalDouble('nothing'), null); |
| expect(() => config.optionalDouble('not_parsable'), throwsFormatException); |
| expect(() => config.optionalDouble('not_parsable2'), throwsFormatException); |
| }); |
| |
| test('stringList and optionalStringList', () { |
| { |
| final config = Config( |
| fileParsed: {}, |
| ); |
| |
| expect(config.optionalStringList('my_list'), null); |
| expect(() => config.stringList('my_list'), throwsFormatException); |
| } |
| |
| { |
| final config = Config( |
| fileParsed: {'my_list': <String>[]}, |
| ); |
| |
| expect(config.optionalStringList('my_list'), <String>[]); |
| expect(config.stringList('my_list'), <String>[]); |
| } |
| }); |
| |
| test('pathList and optionalPathList', () { |
| { |
| final config = Config( |
| fileParsed: {}, |
| ); |
| |
| expect(config.optionalPathList('my_list'), null); |
| expect(() => config.pathList('my_list'), throwsFormatException); |
| } |
| |
| { |
| final config = Config( |
| fileParsed: {'my_list': <String>[]}, |
| ); |
| |
| expect(config.optionalPathList('my_list'), <String>[]); |
| expect(config.pathList('my_list'), <String>[]); |
| } |
| }); |
| |
| test('non-string key maps', () { |
| // This is valid in YAML (not in JSON). |
| // |
| // Such values cannot be accessed with our hierarchical keys, but they can |
| // be accessed with [Config.valueOf]. |
| final config = Config( |
| fileParsed: { |
| 'my_non_string_key_map': { |
| 1: 'asdf', |
| 2: 'foo', |
| }, |
| }, |
| ); |
| |
| expect( |
| config.valueOf<Map<Object, Object?>>('my_non_string_key_map'), |
| { |
| 1: 'asdf', |
| 2: 'foo', |
| }, |
| ); |
| }); |
| |
| test('null values in maps', () { |
| final config = Config( |
| fileParsed: { |
| 'my_non_string_key_map': { |
| 'x': null, |
| 'y': 42, |
| }, |
| }, |
| ); |
| |
| expect( |
| config.valueOf<Map<Object, Object?>>('my_non_string_key_map'), |
| { |
| 'x': null, |
| 'y': 42, |
| }, |
| ); |
| }); |
| } |