commit e8e56309a6a2f1058a2e795657ad604e38371a85 Author: nixo Date: Tue Feb 18 21:56:18 2020 +0100 init (v1.24.3) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3954a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Don’t commit the following directories created by pub. +build/ +packages +.buildlog + +# Or Settings +.settings +.project + +# Or the files created by dart2js. +*.dart.js +*.dart.precompiled.js +*.js_ +*.js.deps +*.js.map + +# Include when developing application packages. +pubspec.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b1279b7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: dart +script: ./tool/travis.sh +sudo: false diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..4396ab5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,11 @@ +# Names should be added to this file with this pattern: +# +# For individuals: +# Name +# +# For organizations: +# Organization +# +Google Inc. <*@google.com> +Sean Eagan + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f75a62f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.3 + +* Assume owner/group matches that of executable until it's possible to check. (#5) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6f5e0ea --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +Want to contribute? Great! First, read this page (including the small print at +the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. + +### File headers +All files in the project must start with the following header. + + // Copyright (c) 2015, 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. + +### The small print +Contributions made by corporations are covered by a different agreement than the +one above, the +[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..095cb70 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright 2015, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e102712 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +which [![pub package](http://img.shields.io/pub/v/which.svg)](https://pub.dartlang.org/packages/which) [![Build Status](https://travis-ci.org/dart-lang/which.svg?branch=master)](https://travis-ci.org/dart-lang/which) [![Coverage Status](https://coveralls.io/repos/dart-lang/which/badge.svg)](https://coveralls.io/r/dart-lang/which) +===== + +Check for and locate installed executables. Just like unix [which(1)][unix_which], except: + +* Doesn't shell out (fast). +* Cross-platform (works on windows). + +## Install + +```shell +pub global activate den +den install which +``` + +## Usage + +```dart +import 'dart:io'; + +import 'package:which/which.dart'; + +main(arguments) async { + + // Asynchronously + var git = await which('git', orElse: () => null); + + // Or synchronously + var git = whichSync('git', orElse: () => null); + + if (git == null) { + print('Please install git and try again'); + exit(1); + } + + await Process.run(git, ['add', '-A']); + await Process.run(git, ['commit', '-m', arguments.first]); +} +``` + +[unix_which]: http://en.wikipedia.org/wiki/Which_%28Unix%29 diff --git a/lib/src/candidate_paths.dart b/lib/src/candidate_paths.dart new file mode 100644 index 0000000..047ba31 --- /dev/null +++ b/lib/src/candidate_paths.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2015, 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. + +library which.src.candidate_paths; + +import 'dart:io'; + +import 'package:path/path.dart'; + +Iterable getCandidatePaths(String command, Map environment, bool isWindows, Context context) { + if (context.isAbsolute(command)) return [command]; + + String getEnvVar(String envVar, String defaultValue) { + var v = environment[envVar]; + return v == null ? defaultValue : v; + } + + var pathVarSeparator = isWindows ? ";" : ":"; + + List splitEnvVar(String envVar, String defaultValue) => + getEnvVar(envVar, defaultValue).split(pathVarSeparator); + + var pathEnv = splitEnvVar('PATH', ''); + + var noExtPaths = + pathEnv.map((pathEntry) => context.join(pathEntry, command)); + + if (!isWindows) return noExtPaths; + + pathEnv.insert(0, context.current); + var pathExt = splitEnvVar('PATHEXT', ".EXE"); + if (command.contains('.')) pathExt.insert(0, ''); + return noExtPaths.expand((commandPath) => + pathExt.map((pathExtEntry) => commandPath + pathExtEntry)); +} + +Iterable getRealCandidatePaths(String command) => + getCandidatePaths(command, Platform.environment, Platform.isWindows, context); diff --git a/lib/src/has_permission.dart b/lib/src/has_permission.dart new file mode 100644 index 0000000..b76eb39 --- /dev/null +++ b/lib/src/has_permission.dart @@ -0,0 +1,55 @@ +// Copyright (c) 2015, 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. + +/// See http://dartbug.com/22036 +library which.src.has_permission; + +class FilePermission { + + final int index; + final String _name; + + const FilePermission._(this.index, this._name); + + static const EXECUTE = const FilePermission._(0, 'EXECUTE'); + static const WRITE = const FilePermission._(1, 'WRITE'); + static const READ = const FilePermission._(2, 'READ'); + static const SET_UID = const FilePermission._(3, 'SET_UID'); + static const SET_GID = const FilePermission._(4, 'SET_GID'); + static const STICKY = const FilePermission._(5, 'STICKY'); + + static const List values = const [EXECUTE, WRITE, READ, SET_UID, SET_GID, STICKY]; + + String toString() => 'FilePermission.$_name'; +} + +class FilePermissionRole { + + final int index; + final String _name; + + const FilePermissionRole._(this.index, this._name); + + static const WORLD = const FilePermissionRole._(0, 'WORLD'); + static const GROUP = const FilePermissionRole._(1, 'GROUP'); + static const OWNER = const FilePermissionRole._(2, 'OWNER'); + + static const List values = const [WORLD, GROUP, OWNER]; + + String toString() => 'FilePermissionRole.$_name'; +} + +bool hasPermission(int fileStatMode, FilePermission permission, {FilePermissionRole role: FilePermissionRole.WORLD}) { + var bitIndex = _getPermissionBitIndex(permission, role); + return (fileStatMode & (1 << bitIndex)) != 0; +} + +int _getPermissionBitIndex(FilePermission permission, FilePermissionRole role) { + switch (permission) { + case FilePermission.SET_UID: return 11; + case FilePermission.SET_GID: return 10; + case FilePermission.STICKY: return 9; + default: return (role.index * 3) + permission.index; + } +} diff --git a/lib/src/is_executable.dart b/lib/src/is_executable.dart new file mode 100644 index 0000000..fb189de --- /dev/null +++ b/lib/src/is_executable.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2015, 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. + +library which.src.is_executable; + +import 'dart:async'; +import 'dart:io'; + +import 'package:when/when.dart'; + +import 'has_permission.dart'; + +Future isExecutable(String path, bool isWindows, Future getStat(path)) => + _isExecutable(path, isWindows, getStat); + +bool isExecutableSync(String path, bool isWindows, FileStat getStat(path)) => + _isExecutable(path, isWindows, getStat); + +_isExecutable(String path, bool isWindows, getStat(path)) => + when(() => getStat(path), onSuccess: (stat) => isExecutableStat(stat, isWindows)); + +/// Tests whether the file exists and is executable. +bool isExecutableStat(FileStat stat, bool isWindows) { + if (FileSystemEntityType.FILE != stat.type) return false; + + // There is no concept of executable on windows. + if (isWindows) return true; + + // TODO: This currently produces false positives (returns true when it + // shouldn't) when the uid/gid of current user and executable don't + // match. Fix if/when uid/gid support is added: + // http://dartbug.com/22037. + return FilePermissionRole.values.any((role) => + hasPermission(stat.mode, FilePermission.EXECUTE, role: role)); +} diff --git a/lib/src/util.dart b/lib/src/util.dart new file mode 100644 index 0000000..faeda94 --- /dev/null +++ b/lib/src/util.dart @@ -0,0 +1,21 @@ +// Copyright (c) 2015, 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. + +library which.src.util; + +import 'dart:async'; + +/// Transparently call `firstWhere` on a [Stream] or [Iterable]. +// TODO: Remove once https://dartbug.com/22028 is fixed. +dynamic firstWhere(sequence, test, { orElse() }) => sequence is Iterable ? + sequence.firstWhere(test, orElse: orElse) : + _streamFirstWhere(sequence, test, orElse: orElse); + +Future _streamFirstWhere(Stream stream, test(item), { orElse() }) { + var pairs = stream.asyncMap((item) => test(item).then((result) => [item, result])); + return pairs.firstWhere((pair) => pair.last, defaultValue: () => [orElse(), null]).then((pair) => pair.first); +} + +/// The identity function simply returns its argument ([x]). +dynamic identity(x) => x; diff --git a/lib/src/which_impl.dart b/lib/src/which_impl.dart new file mode 100644 index 0000000..d06fdbc --- /dev/null +++ b/lib/src/which_impl.dart @@ -0,0 +1,55 @@ +// Copyright (c) 2015, 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. + +library which.src.which_impl; + +import 'dart:async'; + +import 'package:when/when.dart'; + +import 'util.dart'; + +Future which( + String command, + Iterable candidatePaths, + bool isWindows, + Future isExecutable(String path, bool isWindows), + orElse()) => new Future(() => _which( + command, + candidatePaths, + isWindows, + isExecutable, + orElse, + toSequence: (items) => new Stream.fromIterable(items))); + +String whichSync( + String command, + Iterable candidatePaths, + bool isWindows, + bool isExecutable(String path, bool isWindows), + orElse()) => _which( + command, + candidatePaths, + isWindows, + isExecutable, + orElse); + +_which( + String command, + Iterable candidatePaths, + bool isWindows, + isExecutable(String path, bool isWindows), + orElse(), + {toSequence(Iterable items): identity}) => when( + () => firstWhere( + toSequence(candidatePaths), + (path) => isExecutable(path, isWindows), + orElse: orElse != null ? orElse : () => _commandNotFound(command, null)), + onError: (e) => _commandNotFound(command, e)); + +_commandNotFound(String command, e) { + var message = 'Command not found: $command'; + if (e != null) message += '\n$e'; + throw new StateError(message); +} diff --git a/lib/which.dart b/lib/which.dart new file mode 100644 index 0000000..693f427 --- /dev/null +++ b/lib/which.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2015, 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. + +library which; + +import 'dart:async'; +import 'dart:io'; + +import 'src/candidate_paths.dart'; +import 'src/is_executable.dart'; +import 'src/which_impl.dart' as impl; + +/// Returns a future for the first [command] executable in the `PATH`. +/// +/// If [command] is not found, [orElse] is called, which defaults to throwing. +Future which(String command, { orElse() }) => new Future(() => impl.which( + command, + getRealCandidatePaths(command), + Platform.isWindows, + (path, isWindows) => isExecutable(path, isWindows, FileStat.stat), + orElse)); + +/// Returns the first [command] executable in the `PATH`. +/// +/// If [command] is not found, [orElse] is called, which defaults to throwing. +String whichSync(String command, { orElse() }) => impl.whichSync( + command, + getRealCandidatePaths(command), + Platform.isWindows, + (path, isWindows) => isExecutableSync(path, isWindows, FileStat.statSync), + orElse); diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..55014fa --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,15 @@ +name: which +version: 0.1.3 +authors: + Dart Team + Sean Eagan +description: Like unix which(1) - check for and locate installed executables. +homepage: https://github.com/dart-lang/which +environment: + sdk: '>=1.0.0 <2.0.0' +dependencies: + path: '>=1.3.1 <2.0.0' + when: '>=0.2.0 <0.3.0' +dev_dependencies: + mockito: '>=0.8.1 <0.9.0' + unittest: '>=0.11.4 <0.12.0' diff --git a/test/candidate_paths_test.dart b/test/candidate_paths_test.dart new file mode 100644 index 0000000..fdb6fa1 --- /dev/null +++ b/test/candidate_paths_test.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, 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. + +library which.test.candidate_paths; + +import 'package:unittest/unittest.dart'; + +import 'util.dart'; + +main() { + group('getCandidatePaths', () { + test('posix', () { + var candidatePaths = getPosixCandidatePaths('z', '/x/y:/a/b/c', '/foo/bar'); + expect(candidatePaths, ['/x/y/z', '/a/b/c/z']); + }); + + test('windows', () { + var candidatePaths = getWindowsCandidatePaths('z', r'C:\x\y;C:\a\b\c', '.EXE;.BAT', r'C:\foo\bar'); + expect(candidatePaths, [ + r'C:\foo\bar\z.EXE', + r'C:\foo\bar\z.BAT', + r'C:\x\y\z.EXE', + r'C:\x\y\z.BAT', + r'C:\a\b\c\z.EXE', + r'C:\a\b\c\z.BAT']); + }); + }); +} diff --git a/test/has_permission_test.dart b/test/has_permission_test.dart new file mode 100644 index 0000000..383e13e --- /dev/null +++ b/test/has_permission_test.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, 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. + +library which.test.has_permission; + +import 'package:unittest/unittest.dart'; +import 'package:which/src/has_permission.dart'; + +import 'util.dart'; + +main() { + test('hasPermission', () { + var mode = parseBinary('010101010101'); + + expect(hasPermission(mode, FilePermission.SET_UID), isFalse); + expect(hasPermission(mode, FilePermission.SET_GID), isTrue); + expect(hasPermission(mode, FilePermission.STICKY), isFalse); + expect(hasPermission(mode, FilePermission.READ, role: FilePermissionRole.OWNER), isTrue); + expect(hasPermission(mode, FilePermission.WRITE, role: FilePermissionRole.OWNER), isFalse); + expect(hasPermission(mode, FilePermission.EXECUTE, role: FilePermissionRole.OWNER), isTrue); + expect(hasPermission(mode, FilePermission.READ, role: FilePermissionRole.GROUP), isFalse); + expect(hasPermission(mode, FilePermission.WRITE, role: FilePermissionRole.GROUP), isTrue); + expect(hasPermission(mode, FilePermission.EXECUTE, role: FilePermissionRole.GROUP), isFalse); + expect(hasPermission(mode, FilePermission.READ, role: FilePermissionRole.WORLD), isTrue); + expect(hasPermission(mode, FilePermission.WRITE, role: FilePermissionRole.WORLD), isFalse); + expect(hasPermission(mode, FilePermission.EXECUTE, role: FilePermissionRole.WORLD), isTrue); + }); +} diff --git a/test/is_executable_test.dart b/test/is_executable_test.dart new file mode 100644 index 0000000..5fbd6a2 --- /dev/null +++ b/test/is_executable_test.dart @@ -0,0 +1,77 @@ +// Copyright (c) 2015, 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. + +library which.test.is_executable; + +import 'dart:io'; + +import 'package:mockito/mockito.dart'; +import 'package:unittest/unittest.dart'; +import 'package:which/src/is_executable.dart'; + +import 'util.dart'; + +main() { + group('isExecutableStat', () { + test('false if not a file', () { + + var stat = new MockFileStat(); + + // A directory. + when(stat.type).thenReturn(FileSystemEntityType.DIRECTORY); + + var result = isExecutableStat(stat, false); + + expect(result, isFalse); + + verifyNever(stat.mode); + }); + + test('true for all files on windows', () { + + var stat = new MockFileStat(); + + // A file. + when(stat.type).thenReturn(FileSystemEntityType.FILE); + + var result = isExecutableStat(stat, true); + + expect(result, isTrue); + + verifyNever(stat.mode); + }); + + test('true if has world execute permission', () { + var result = isExecutableStat(_getMockFileStat('000000000001'), false); + expect(result, isTrue); + }); + + test('true if has group execute permission', () { + var result = isExecutableStat(_getMockFileStat('000000001000'), false); + expect(result, isTrue); + }); + + test('true if has owner execute permission', () { + var result = isExecutableStat(_getMockFileStat('000001000000'), false); + expect(result, isTrue); + }); + + test('false if has no execute permissions', () { + var result = isExecutableStat(_getMockFileStat('111110110110'), false); + expect(result, isFalse); + }); + }); +} + +MockFileStat _getMockFileStat(String mode) { + var stat = new MockFileStat(); + + // A file. + when(stat.type).thenReturn(FileSystemEntityType.FILE); + + // Last bit is world execute. + when(stat.mode).thenReturn(int.parse(mode, radix: 2)); + + return stat; +} diff --git a/test/test.dart b/test/test.dart new file mode 100644 index 0000000..09e16ee --- /dev/null +++ b/test/test.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2015, 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. + +library which.test; + +import 'candidate_paths_test.dart' as candidate_paths; +import 'has_permission_test.dart' as has_permission; +import 'is_executable_test.dart' as is_exe; +import 'which_test.dart' as which; +import 'which_impl_test.dart' as which_impl; + +main() { + candidate_paths.main(); + has_permission.main(); + is_exe.main(); + which.main(); + which_impl.main(); +} diff --git a/test/util.dart b/test/util.dart new file mode 100644 index 0000000..2818d71 --- /dev/null +++ b/test/util.dart @@ -0,0 +1,41 @@ +// Copyright (c) 2015, 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. + +library which.test.util; + +import 'dart:io'; + +import 'package:mockito/mockito.dart'; +import 'package:path/path.dart' as path; +import 'package:which/src/candidate_paths.dart'; + +getPosixCandidatePaths(String command, String pathVar, String current) { + var env = { + 'PATH': pathVar + }; + var isWindows = false; + var context = new path.Context(style: path.Style.posix, current: current); + + return getCandidatePaths(command, env, isWindows, context); +} + +getWindowsCandidatePaths(String command, String pathVar, String pathExtVar, String current) { + var env = { + 'PATH': pathVar, + 'PATHEXT': pathExtVar + }; + var isWindows = true; + var context = new path.Context(style: path.Style.windows, current: current); + + return getCandidatePaths(command, env, isWindows, context); +} + +class MockFileStat extends Mock implements FileStat { + + MockFileStat(); + + noSuchMethod(i) => super.noSuchMethod(i); +} + +int parseBinary(String b) => int.parse(b, radix: 2); diff --git a/test/which_impl_test.dart b/test/which_impl_test.dart new file mode 100644 index 0000000..7dc9a72 --- /dev/null +++ b/test/which_impl_test.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2015, 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. + +library which.test.which_impl; + +import 'dart:async'; + +import 'package:unittest/unittest.dart'; +import 'package:which/src/which_impl.dart'; + +import 'util.dart'; + +main() { + group('which', () { + test('should complete to the first matching executable in candidate paths', () { + var candidatePaths = getPosixCandidatePaths('z', '/x/y:/a/b/c', '/foo/bar'); + + return which('z', candidatePaths, false, (path, isWindows) => new Future.value(path == '/x/y/z' || path == '/a/b/c/z'), null) + .then((path) => expect(path, '/x/y/z')); + }); + + test('should call orElse if command not found', () { + var candidatePaths = getPosixCandidatePaths('z', '/x/y:/a/b/c', '/foo/bar'); + + return which('z', candidatePaths, false, (path, isWindows) => new Future.value(false), () => '/or/else') + .then((path) => expect(path, '/or/else')); + }); + + test('should throw state error if command not found and orElse not provided', () { + var future = new Future(() => + which('z', [], false, (path, isWindows) => new Future.value(false), null)); + + expect(future, throwsStateError); + }); +}); + + group('whichSync', () { + test('should return the first matching executable in candidate paths', () { + var candidatePaths = getWindowsCandidatePaths('z', r'C:\x\y;C:\a\b\c', '.EXE;.BAT', r'C:\foo\bar'); + + var result = whichSync('find', candidatePaths, true, (path, isWindows) => path == r'C:\x\y\z.BAT' || path == r'C:\a\b\c\z.BAT', null); + + expect(result, r'C:\x\y\z.BAT'); + }); + + test('should call orElse if command not found', () { + var candidatePaths = getWindowsCandidatePaths('z', r'C:\x\y;C:\a\b\c', '.EXE;.BAT', r'C:\foo\bar'); + + var result = whichSync('find', candidatePaths, true, (path, isWindows) => false, () => r'C:\or\else'); + + expect(result, r'C:\or\else'); + }); + test('should throw state error if command not found and orElse not provided', () { + expect(() => whichSync('z', [], true, (path, isWindows) => false, null), throwsStateError); + }); + }); +} diff --git a/test/which_test.dart b/test/which_test.dart new file mode 100644 index 0000000..b3c21f6 --- /dev/null +++ b/test/which_test.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, 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. + +library which.test.which; + +import 'package:path/path.dart'; +import 'package:unittest/unittest.dart'; +import 'package:which/which.dart'; + +main() { + group('which', () { + // Any dart:io supported platform (*nix, osx, windows) should have `find`. + test('should find `find`', () => which('find').then(_testResult)); + }); + + group('whichSync', () { + // Any dart:io supported platform (*nix, osx, windows) should have `find`. + test('should find `find`', () { + _testResult(whichSync('find')); + }); + }); +} + +_testResult(String path) { + expect(path, isNotNull); + var base = basenameWithoutExtension(path); + expect(base, 'find'); +} diff --git a/tool/travis.sh b/tool/travis.sh new file mode 100755 index 0000000..f534076 --- /dev/null +++ b/tool/travis.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Copyright (c) 2015, 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. + +# Fast fail the script on failures. +set -e + +# Verify that the libraries are error free. +dartanalyzer --fatal-warnings \ + lib/which.dart \ + test/test.dart + +# Run the tests. +dart test/test.dart + +# Install dart_coveralls; gather and send coverage data. +if [ "$COVERALLS_TOKEN" ]; then + pub global activate dart_coveralls 0.1.11 + pub global run dart_coveralls report \ + --token $COVERALLS_TOKEN \ + --retry 2 \ + --exclude-test-files \ + test/test.dart +fi