diff --git a/.github/ISSUE_TEMPLATE/platform.md b/.github/ISSUE_TEMPLATE/platform.md new file mode 100644 index 000000000..4a168fdf1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/platform.md @@ -0,0 +1,5 @@ +--- +name: "package:platform" +about: "Create a bug or file a feature request against package:platform." +labels: "package:platform" +--- \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml index 03fe1f472..ce828680f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -35,3 +35,7 @@ "package:path": - changed-files: - any-glob-to-any-file: 'pkgs/path/**' + +"package:platform": + - changed-files: + - any-glob-to-any-file: 'pkgs/platform/**' diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml index f258f8b69..cf26962ac 100644 --- a/.github/workflows/health.yaml +++ b/.github/workflows/health.yaml @@ -9,6 +9,6 @@ jobs: uses: dart-lang/ecosystem/.github/workflows/health.yaml@main with: ignore_coverage: "**.mock.dart,**.g.dart" - ignore_license: "**.mock.dart,**.g.dart,**.mocks.dart" + ignore_license: "**.mock.dart,**.g.dart,**.mocks.dart,pkgs/platform/*" permissions: pull-requests: write diff --git a/.github/workflows/platform.yaml b/.github/workflows/platform.yaml new file mode 100644 index 000000000..52c9c4697 --- /dev/null +++ b/.github/workflows/platform.yaml @@ -0,0 +1,52 @@ +name: package:platform + +on: + # Run CI on pushes to the main branch, and on PRs against main. + push: + branches: [ main ] + paths: + - '.github/workflows/platform.yaml' + - 'pkgs/platform/**' + pull_request: + branches: [ main ] + paths: + - '.github/workflows/platform.yaml' + - 'pkgs/platform/**' + schedule: + - cron: "0 0 * * 0" +env: + PUB_ENVIRONMENT: bot.github + +defaults: + run: + working-directory: pkgs/platform/ + +jobs: + correctness: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 + with: + sdk: dev + - name: Install dependencies + run: dart pub upgrade + - name: Verify formatting + run: dart format --output=none --set-exit-if-changed . + - name: Analyze project source + run: dart analyze --fatal-infos + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + sdk: [stable, beta, dev] + steps: + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + - uses: dart-lang/setup-dart@9a04e6d73cca37bd455e0608d7e5092f881fd603 + with: + sdk: ${{ matrix.sdk }} + - name: Install dependencies + run: dart pub upgrade + - name: Run Tests + run: dart test diff --git a/README.md b/README.md index 4565c8186..8adfa3a9d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This repository is home to various Dart packages under the [dart.dev](https://pu | [logging](pkgs/logging/) | Provides APIs for debugging and error logging. | [![pub package](https://img.shields.io/pub/v/logging.svg)](https://pub.dev/packages/logging) | | [os_detect](pkgs/os_detect/) | Platform independent OS detection. | [![pub package](https://img.shields.io/pub/v/os_detect.svg)](https://pub.dev/packages/os_detect) | | [path](pkgs/path/) | A string-based path manipulation library for all of the path operations you know and love. | [![pub package](https://img.shields.io/pub/v/path.svg)](https://pub.dev/packages/path) | +| [platform](pkgs/platform/) | A pluggable, mockable platform information abstraction for Dart. | [![pub package](https://img.shields.io/pub/v/platform.svg)](https://pub.dev/packages/platform) | ## Publishing automation diff --git a/pkgs/platform/.gitignore b/pkgs/platform/.gitignore new file mode 100644 index 000000000..dfbf1b43f --- /dev/null +++ b/pkgs/platform/.gitignore @@ -0,0 +1,17 @@ +### Dart template +# Don’t commit the following directories created by pub. +.buildlog +.dart_tool/ +.pub/ +build/ +packages +.packages + +# Include when developing application packages. +pubspec.lock + +# IDE +.project +.settings +.idea +.c9 diff --git a/pkgs/platform/AUTHORS b/pkgs/platform/AUTHORS new file mode 100644 index 000000000..ad59f1184 --- /dev/null +++ b/pkgs/platform/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the Process project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. diff --git a/pkgs/platform/CHANGELOG.md b/pkgs/platform/CHANGELOG.md new file mode 100644 index 000000000..1e8c4d574 --- /dev/null +++ b/pkgs/platform/CHANGELOG.md @@ -0,0 +1,111 @@ +## 3.1.6 + +* Move to `dart-lang/core` monorepo. + +## 3.1.5 + +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. +* Transfers the package source from https://github.com/flutter/packages + to https://github.com/dart-lang/platform. + +## 3.1.4 + +* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. +* Fixes new lint warnings. + +## 3.1.3 + +* Adds example app. + +## 3.1.2 + +* Adds pub topics to package metadata. +* Updates minimum supported SDK version to Flutter 3.7/Dart 2.19. + +## 3.1.1 + +* Transfers the package source from https://github.com/google/platform.dart to + https://github.com/flutter/packages. + +## 3.1.0 + +* Removed `Platform.packageRoot`, which was already marked deprecated, and which + didn't work in Dart 2. + +## 3.0.2 + +* Added `FakePlatform.copyWith` function. + +## 3.0.1 + +* Added string constants for each of the supported platforms for use in switch + statements. + +## 3.0.0 + +* First stable null safe release. + +## 3.0.0-nullsafety.4 + +* Update supported SDK range. + +## 3.0.0-nullsafety.3 + +* Update supported SDK range. + +## 3.0.0-nullsafety.2 + +* Update supported SDK range. + +## 3.0.0-nullsafety.1 + +* Migrate package to null-safe dart. + +## 2.2.1 + +* Add `operatingSystemVersion` + +## 2.2.0 + +* Declare compatibility with Dart 2 stable +* Update dependency on `package:test` to 1.0 + +## 2.1.2 + +* Relax sdk upper bound constraint to '<2.0.0' to allow 'edge' dart sdk use. + +## 2.1.1 + +* Bumped maximum Dart SDK version to 2.0.0-dev.infinity + +## 2.1.0 + +* Added `localeName` +* Bumped minimum Dart SDK version to 1.24.0-dev.0.0 + +## 2.0.0 + +* Added `stdinSupportsAnsi` and `stdinSupportsAnsi` +* Removed `ansiSupported` + +## 1.1.1 + +* Updated `LocalPlatform` to use new `dart.io` API for ansi color support queries +* Bumped minimum Dart SDK version to 1.23.0-dev.10.0 + +## 1.1.0 + +* Added `ansiSupported` +* Bumped minimum Dart SDK version to 1.23.0-dev.9.0 + +## 1.0.2 + +* Minor doc updates + +## 1.0.1 + +* Added const constructors for `Platform` and `LocalPlatform` + +## 1.0.0 + +* Initial version diff --git a/pkgs/platform/LICENSE b/pkgs/platform/LICENSE new file mode 100644 index 000000000..389ce9856 --- /dev/null +++ b/pkgs/platform/LICENSE @@ -0,0 +1,26 @@ +Copyright 2017, 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. diff --git a/pkgs/platform/README.md b/pkgs/platform/README.md new file mode 100644 index 000000000..44932a672 --- /dev/null +++ b/pkgs/platform/README.md @@ -0,0 +1,10 @@ +[![Pub](https://img.shields.io/pub/v/platform.svg)](https://pub.dartlang.org/packages/platform) + +A generic platform abstraction for Dart. + +Like `dart:io`, `package:platform` supplies a rich, Dart-idiomatic API for +accessing platform-specific information. + +`package:platform` provides a lightweight wrapper around the static `Platform` +properties that exist in `dart:io`. However, it uses instance properties rather +than static properties, making it possible to mock out in tests. diff --git a/pkgs/platform/analysis_options.yaml b/pkgs/platform/analysis_options.yaml new file mode 100644 index 000000000..454444233 --- /dev/null +++ b/pkgs/platform/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + errors: + # Allow having TODOs in the code + todo: ignore diff --git a/pkgs/platform/example/.gitignore b/pkgs/platform/example/.gitignore new file mode 100644 index 000000000..3a8579040 --- /dev/null +++ b/pkgs/platform/example/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/pkgs/platform/example/README.md b/pkgs/platform/example/README.md new file mode 100644 index 000000000..b0bf20bf8 --- /dev/null +++ b/pkgs/platform/example/README.md @@ -0,0 +1,2 @@ +A small example application demonstrating how to use the platform information +APIs in `package:platform`. \ No newline at end of file diff --git a/pkgs/platform/example/analysis_options.yaml b/pkgs/platform/example/analysis_options.yaml new file mode 100644 index 000000000..dee8927aa --- /dev/null +++ b/pkgs/platform/example/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/pkgs/platform/example/bin/example.dart b/pkgs/platform/example/bin/example.dart new file mode 100644 index 000000000..59693e358 --- /dev/null +++ b/pkgs/platform/example/bin/example.dart @@ -0,0 +1,15 @@ +import 'package:platform/platform.dart'; + +void main(List arguments) { + const LocalPlatform platform = LocalPlatform(); + + print('Operating System: ${platform.operatingSystem}.'); + print('Local Hostname: ${platform.localHostname}.'); + print('Number of Processors: ${platform.numberOfProcessors}.'); + print('Path Separator: ${platform.pathSeparator}.'); + print('Locale Name: ${platform.localeName}.'); + print('Stdin Supports ANSI: ${platform.stdinSupportsAnsi}.'); + print('Stdout Supports ANSI: ${platform.stdoutSupportsAnsi}.'); + print('Executable Arguments: ${platform.executableArguments}.'); + print('Dart Version: ${platform.version}.'); +} diff --git a/pkgs/platform/example/pubspec.yaml b/pkgs/platform/example/pubspec.yaml new file mode 100644 index 000000000..35107a7fd --- /dev/null +++ b/pkgs/platform/example/pubspec.yaml @@ -0,0 +1,14 @@ +name: example +description: Demonstrates how to use the platform api. +version: 1.0.0 +publish_to: none + +environment: + sdk: ^3.2.0 + +dependencies: + platform: + path: ../ + +dev_dependencies: + lints: ^4.0.0 diff --git a/pkgs/platform/lib/platform.dart b/pkgs/platform/lib/platform.dart new file mode 100644 index 000000000..f096dcb41 --- /dev/null +++ b/pkgs/platform/lib/platform.dart @@ -0,0 +1,8 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Core interfaces & classes. +export 'src/interface/local_platform.dart'; +export 'src/interface/platform.dart'; +export 'src/testing/fake_platform.dart'; diff --git a/pkgs/platform/lib/src/interface/local_platform.dart b/pkgs/platform/lib/src/interface/local_platform.dart new file mode 100644 index 000000000..c24c01be5 --- /dev/null +++ b/pkgs/platform/lib/src/interface/local_platform.dart @@ -0,0 +1,58 @@ +// Copyright 2013 The Flutter Authors. 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:io' as io show Platform, stdin, stdout; + +import 'platform.dart'; + +/// `Platform` implementation that delegates directly to `dart:io`. +class LocalPlatform extends Platform { + /// Creates a new [LocalPlatform]. + const LocalPlatform(); + + @override + int get numberOfProcessors => io.Platform.numberOfProcessors; + + @override + String get pathSeparator => io.Platform.pathSeparator; + + @override + String get operatingSystem => io.Platform.operatingSystem; + + @override + String get operatingSystemVersion => io.Platform.operatingSystemVersion; + + @override + String get localHostname => io.Platform.localHostname; + + @override + Map get environment => io.Platform.environment; + + @override + String get executable => io.Platform.executable; + + @override + String get resolvedExecutable => io.Platform.resolvedExecutable; + + @override + Uri get script => io.Platform.script; + + @override + List get executableArguments => io.Platform.executableArguments; + + @override + String? get packageConfig => io.Platform.packageConfig; + + @override + String get version => io.Platform.version; + + @override + bool get stdinSupportsAnsi => io.stdin.supportsAnsiEscapes; + + @override + bool get stdoutSupportsAnsi => io.stdout.supportsAnsiEscapes; + + @override + String get localeName => io.Platform.localeName; +} diff --git a/pkgs/platform/lib/src/interface/platform.dart b/pkgs/platform/lib/src/interface/platform.dart new file mode 100644 index 000000000..dcc47be82 --- /dev/null +++ b/pkgs/platform/lib/src/interface/platform.dart @@ -0,0 +1,205 @@ +// Copyright 2013 The Flutter Authors. 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'; + +/// Provides API parity with the `Platform` class in `dart:io`, but using +/// instance properties rather than static properties. This difference enables +/// the use of these APIs in tests, where you can provide mock implementations. +abstract class Platform { + /// Creates a new [Platform]. + const Platform(); + + /// A string constant to compare with [operatingSystem] to see if the platform + /// is Linux. + /// + /// Useful in case statements when switching on [operatingSystem]. + /// + /// To just check if the platform is Linux, use [isLinux]. + static const String linux = 'linux'; + + /// A string constant to compare with [operatingSystem] to see if the platform + /// is Windows. + /// + /// Useful in case statements when switching on [operatingSystem]. + /// + /// To just check if the platform is Windows, use [isWindows]. + static const String windows = 'windows'; + + /// A string constant to compare with [operatingSystem] to see if the platform + /// is macOS. + /// + /// Useful in case statements when switching on [operatingSystem]. + /// + /// To just check if the platform is macOS, use [isMacOS]. + static const String macOS = 'macos'; + + /// A string constant to compare with [operatingSystem] to see if the platform + /// is Android. + /// + /// Useful in case statements when switching on [operatingSystem]. + /// + /// To just check if the platform is Android, use [isAndroid]. + static const String android = 'android'; + + /// A string constant to compare with [operatingSystem] to see if the platform + /// is iOS. + /// + /// Useful in case statements when switching on [operatingSystem]. + /// + /// To just check if the platform is iOS, use [isIOS]. + static const String iOS = 'ios'; + + /// A string constant to compare with [operatingSystem] to see if the platform + /// is Fuchsia. + /// + /// Useful in case statements when switching on [operatingSystem]. + /// + /// To just check if the platform is Fuchsia, use [isFuchsia]. + static const String fuchsia = 'fuchsia'; + + /// A list of the possible values that [operatingSystem] can return. + static const List operatingSystemValues = [ + linux, + macOS, + windows, + android, + iOS, + fuchsia, + ]; + + /// The number of processors of the machine. + int get numberOfProcessors; + + /// The path separator used by the operating system to separate + /// components in file paths. + String get pathSeparator; + + /// A string (`linux`, `macos`, `windows`, `android`, `ios`, or `fuchsia`) + /// representing the operating system. + /// + /// The possible return values are available from [operatingSystemValues], and + /// there are constants for each of the platforms to use in switch statements + /// or conditionals (See [linux], [macOS], [windows], [android], [iOS], and + /// [fuchsia]). + String get operatingSystem; + + /// A string representing the version of the operating system or platform. + String get operatingSystemVersion; + + /// Get the local hostname for the system. + String get localHostname; + + /// True if the operating system is Linux. + bool get isLinux => operatingSystem == linux; + + /// True if the operating system is OS X. + bool get isMacOS => operatingSystem == macOS; + + /// True if the operating system is Windows. + bool get isWindows => operatingSystem == windows; + + /// True if the operating system is Android. + bool get isAndroid => operatingSystem == android; + + /// True if the operating system is iOS. + bool get isIOS => operatingSystem == iOS; + + /// True if the operating system is Fuchsia + bool get isFuchsia => operatingSystem == fuchsia; + + /// The environment for this process. + /// + /// The returned environment is an unmodifiable map whose content is + /// retrieved from the operating system on its first use. + /// + /// Environment variables on Windows are case-insensitive. The map + /// returned on Windows is therefore case-insensitive and will convert + /// all keys to upper case. On other platforms the returned map is + /// a standard case-sensitive map. + Map get environment; + + /// The path of the executable used to run the script in this isolate. + /// + /// The path returned is the literal path used to run the script. This + /// path might be relative or just be a name from which the executable + /// was found by searching the `PATH`. + /// + /// To get the absolute path to the resolved executable use + /// [resolvedExecutable]. + String get executable; + + /// The path of the executable used to run the script in this + /// isolate after it has been resolved by the OS. + /// + /// This is the absolute path, with all symlinks resolved, to the + /// executable used to run the script. + String get resolvedExecutable; + + /// The absolute URI of the script being run in this + /// isolate. + /// + /// If the script argument on the command line is relative, + /// it is resolved to an absolute URI before fetching the script, and + /// this absolute URI is returned. + /// + /// URI resolution only does string manipulation on the script path, and this + /// may be different from the file system's path resolution behavior. For + /// example, a symbolic link immediately followed by '..' will not be + /// looked up. + /// + /// If the executable environment does not support [script] an empty + /// [Uri] is returned. + Uri get script; + + /// The flags passed to the executable used to run the script in this + /// isolate. These are the command-line flags between the executable name + /// and the script name. Each fetch of `executableArguments` returns a new + /// list containing the flags passed to the executable. + List get executableArguments; + + /// The value of the `--packages` flag passed to the executable + /// used to run the script in this isolate. This is the configuration which + /// specifies how Dart packages are looked up. + /// + /// If there is no `--packages` flag, `null` is returned. + String? get packageConfig; + + /// The version of the current Dart runtime. + /// + /// The returned `String` is formatted as the [semver](http://semver.org) + /// version string of the current dart runtime, possibly followed by + /// whitespace and other version and build details. + String get version; + + /// When stdin is connected to a terminal, whether ANSI codes are supported. + bool get stdinSupportsAnsi; + + /// When stdout is connected to a terminal, whether ANSI codes are supported. + bool get stdoutSupportsAnsi; + + /// Get the name of the current locale. + String get localeName; + + /// Returns a JSON-encoded representation of this platform. + String toJson() { + return const JsonEncoder.withIndent(' ').convert({ + 'numberOfProcessors': numberOfProcessors, + 'pathSeparator': pathSeparator, + 'operatingSystem': operatingSystem, + 'operatingSystemVersion': operatingSystemVersion, + 'localHostname': localHostname, + 'environment': environment, + 'executable': executable, + 'resolvedExecutable': resolvedExecutable, + 'script': script.toString(), + 'executableArguments': executableArguments, + 'packageConfig': packageConfig, + 'version': version, + 'stdinSupportsAnsi': stdinSupportsAnsi, + 'stdoutSupportsAnsi': stdoutSupportsAnsi, + 'localeName': localeName, + }); + } +} diff --git a/pkgs/platform/lib/src/testing/fake_platform.dart b/pkgs/platform/lib/src/testing/fake_platform.dart new file mode 100644 index 000000000..0c028ca42 --- /dev/null +++ b/pkgs/platform/lib/src/testing/fake_platform.dart @@ -0,0 +1,199 @@ +// Copyright 2013 The Flutter Authors. 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 '../interface/platform.dart'; + +/// Provides a mutable implementation of the [Platform] interface. +class FakePlatform extends Platform { + /// Creates a new [FakePlatform] with the specified properties. + /// + /// Unspecified properties will *not* be assigned default values (they will + /// remain `null`). If an unset non-null value is read, a [StateError] will + /// be thrown instead of returning `null`. + FakePlatform({ + int? numberOfProcessors, + String? pathSeparator, + String? operatingSystem, + String? operatingSystemVersion, + String? localHostname, + Map? environment, + String? executable, + String? resolvedExecutable, + Uri? script, + List? executableArguments, + this.packageConfig, + String? version, + bool? stdinSupportsAnsi, + bool? stdoutSupportsAnsi, + String? localeName, + }) : _numberOfProcessors = numberOfProcessors, + _pathSeparator = pathSeparator, + _operatingSystem = operatingSystem, + _operatingSystemVersion = operatingSystemVersion, + _localHostname = localHostname, + _environment = environment, + _executable = executable, + _resolvedExecutable = resolvedExecutable, + _script = script, + _executableArguments = executableArguments, + _version = version, + _stdinSupportsAnsi = stdinSupportsAnsi, + _stdoutSupportsAnsi = stdoutSupportsAnsi, + _localeName = localeName; + + /// Creates a new [FakePlatform] with properties whose initial values mirror + /// the specified [platform]. + FakePlatform.fromPlatform(Platform platform) + : _numberOfProcessors = platform.numberOfProcessors, + _pathSeparator = platform.pathSeparator, + _operatingSystem = platform.operatingSystem, + _operatingSystemVersion = platform.operatingSystemVersion, + _localHostname = platform.localHostname, + _environment = Map.from(platform.environment), + _executable = platform.executable, + _resolvedExecutable = platform.resolvedExecutable, + _script = platform.script, + _executableArguments = List.from(platform.executableArguments), + packageConfig = platform.packageConfig, + _version = platform.version, + _stdinSupportsAnsi = platform.stdinSupportsAnsi, + _stdoutSupportsAnsi = platform.stdoutSupportsAnsi, + _localeName = platform.localeName; + + /// Creates a new [FakePlatform] with properties extracted from the encoded + /// JSON string. + /// + /// [json] must be a JSON string that matches the encoding produced by + /// [toJson]. + factory FakePlatform.fromJson(String json) { + final map = const JsonDecoder().convert(json) as Map; + return FakePlatform( + numberOfProcessors: map['numberOfProcessors'] as int?, + pathSeparator: map['pathSeparator'] as String?, + operatingSystem: map['operatingSystem'] as String?, + operatingSystemVersion: map['operatingSystemVersion'] as String?, + localHostname: map['localHostname'] as String?, + environment: + (map['environment'] as Map).cast(), + executable: map['executable'] as String?, + resolvedExecutable: map['resolvedExecutable'] as String?, + script: Uri.parse(map['script'] as String), + executableArguments: + (map['executableArguments'] as List).cast(), + packageConfig: map['packageConfig'] as String?, + version: map['version'] as String?, + stdinSupportsAnsi: map['stdinSupportsAnsi'] as bool?, + stdoutSupportsAnsi: map['stdoutSupportsAnsi'] as bool?, + localeName: map['localeName'] as String?, + ); + } + + /// Creates a new [FakePlatform] from this one, with some properties replaced + /// by the given properties. + FakePlatform copyWith({ + int? numberOfProcessors, + String? pathSeparator, + String? operatingSystem, + String? operatingSystemVersion, + String? localHostname, + Map? environment, + String? executable, + String? resolvedExecutable, + Uri? script, + List? executableArguments, + String? packageConfig, + String? version, + bool? stdinSupportsAnsi, + bool? stdoutSupportsAnsi, + String? localeName, + }) { + return FakePlatform( + numberOfProcessors: numberOfProcessors ?? this.numberOfProcessors, + pathSeparator: pathSeparator ?? this.pathSeparator, + operatingSystem: operatingSystem ?? this.operatingSystem, + operatingSystemVersion: + operatingSystemVersion ?? this.operatingSystemVersion, + localHostname: localHostname ?? this.localHostname, + environment: environment ?? this.environment, + executable: executable ?? this.executable, + resolvedExecutable: resolvedExecutable ?? this.resolvedExecutable, + script: script ?? this.script, + executableArguments: executableArguments ?? this.executableArguments, + packageConfig: packageConfig ?? this.packageConfig, + version: version ?? this.version, + stdinSupportsAnsi: stdinSupportsAnsi ?? this.stdinSupportsAnsi, + stdoutSupportsAnsi: stdoutSupportsAnsi ?? this.stdoutSupportsAnsi, + localeName: localeName ?? this.localeName, + ); + } + + @override + int get numberOfProcessors => _throwIfNull(_numberOfProcessors); + int? _numberOfProcessors; + + @override + String get pathSeparator => _throwIfNull(_pathSeparator); + String? _pathSeparator; + + @override + String get operatingSystem => _throwIfNull(_operatingSystem); + String? _operatingSystem; + + @override + String get operatingSystemVersion => _throwIfNull(_operatingSystemVersion); + String? _operatingSystemVersion; + + @override + String get localHostname => _throwIfNull(_localHostname); + String? _localHostname; + + @override + Map get environment => _throwIfNull(_environment); + Map? _environment; + + @override + String get executable => _throwIfNull(_executable); + String? _executable; + + @override + String get resolvedExecutable => _throwIfNull(_resolvedExecutable); + String? _resolvedExecutable; + + @override + Uri get script => _throwIfNull(_script); + Uri? _script; + + @override + List get executableArguments => _throwIfNull(_executableArguments); + List? _executableArguments; + + @override + String? packageConfig; + + @override + String get version => _throwIfNull(_version); + String? _version; + + @override + bool get stdinSupportsAnsi => _throwIfNull(_stdinSupportsAnsi); + bool? _stdinSupportsAnsi; + + @override + bool get stdoutSupportsAnsi => _throwIfNull(_stdoutSupportsAnsi); + bool? _stdoutSupportsAnsi; + + @override + String get localeName => _throwIfNull(_localeName); + String? _localeName; + + T _throwIfNull(T? value) { + if (value == null) { + throw StateError( + 'Tried to read property of FakePlatform but it was unset.'); + } + return value; + } +} diff --git a/pkgs/platform/pubspec.yaml b/pkgs/platform/pubspec.yaml new file mode 100644 index 000000000..250767240 --- /dev/null +++ b/pkgs/platform/pubspec.yaml @@ -0,0 +1,17 @@ +name: platform +description: A pluggable, mockable platform information abstraction for Dart. +repository: https://github.com/dart-lang/core/tree/main/pkgs/platform +issue_tracker: https://github.com/dart-lang/core/issues +version: 3.1.6 + +topics: + - information + - platform + +environment: + sdk: ^3.2.0 + +dev_dependencies: + dart_flutter_team_lints: ^3.1.0 + test: ^1.16.8 + diff --git a/pkgs/platform/test/fake_platform_test.dart b/pkgs/platform/test/fake_platform_test.dart new file mode 100644 index 000000000..1aa7cbf6c --- /dev/null +++ b/pkgs/platform/test/fake_platform_test.dart @@ -0,0 +1,165 @@ +// Copyright 2013 The Flutter Authors. 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:io' as io; + +import 'package:platform/platform.dart'; +import 'package:test/test.dart'; + +void _expectPlatformsEqual(Platform actual, Platform expected) { + expect(actual.numberOfProcessors, expected.numberOfProcessors); + expect(actual.pathSeparator, expected.pathSeparator); + expect(actual.operatingSystem, expected.operatingSystem); + expect(actual.operatingSystemVersion, expected.operatingSystemVersion); + expect(actual.localHostname, expected.localHostname); + expect(actual.environment, expected.environment); + expect(actual.executable, expected.executable); + expect(actual.resolvedExecutable, expected.resolvedExecutable); + expect(actual.script, expected.script); + expect(actual.executableArguments, expected.executableArguments); + expect(actual.packageConfig, expected.packageConfig); + expect(actual.version, expected.version); + expect(actual.localeName, expected.localeName); +} + +void main() { + group('FakePlatform', () { + late FakePlatform fake; + late LocalPlatform local; + + setUp(() { + fake = FakePlatform(); + local = const LocalPlatform(); + }); + + group('fromPlatform', () { + setUp(() { + fake = FakePlatform.fromPlatform(local); + }); + + test('copiesAllProperties', () { + _expectPlatformsEqual(fake, local); + }); + + test('convertsPropertiesToMutable', () { + final key = fake.environment.keys.first; + + expect(fake.environment[key], local.environment[key]); + fake.environment[key] = 'FAKE'; + expect(fake.environment[key], 'FAKE'); + + expect( + fake.executableArguments.length, local.executableArguments.length); + fake.executableArguments.add('ARG'); + expect(fake.executableArguments.last, 'ARG'); + }); + }); + + group('copyWith', () { + setUp(() { + fake = FakePlatform.fromPlatform(local); + }); + + test('overrides a value, but leaves others intact', () { + final copy = fake.copyWith( + numberOfProcessors: -1, + ); + expect(copy.numberOfProcessors, equals(-1)); + expect(copy.pathSeparator, local.pathSeparator); + expect(copy.operatingSystem, local.operatingSystem); + expect(copy.operatingSystemVersion, local.operatingSystemVersion); + expect(copy.localHostname, local.localHostname); + expect(copy.environment, local.environment); + expect(copy.executable, local.executable); + expect(copy.resolvedExecutable, local.resolvedExecutable); + expect(copy.script, local.script); + expect(copy.executableArguments, local.executableArguments); + expect(copy.packageConfig, local.packageConfig); + expect(copy.version, local.version); + expect(copy.localeName, local.localeName); + }); + test('can override all values', () { + fake = FakePlatform( + numberOfProcessors: 8, + pathSeparator: ':', + operatingSystem: 'fake', + operatingSystemVersion: '0.1.0', + localHostname: 'host', + environment: {'PATH': '.'}, + executable: 'executable', + resolvedExecutable: '/executable', + script: Uri.file('/platform/test/fake_platform_test.dart'), + executableArguments: ['scriptarg'], + version: '0.1.1', + stdinSupportsAnsi: false, + stdoutSupportsAnsi: true, + localeName: 'local', + ); + final copy = fake.copyWith( + numberOfProcessors: local.numberOfProcessors, + pathSeparator: local.pathSeparator, + operatingSystem: local.operatingSystem, + operatingSystemVersion: local.operatingSystemVersion, + localHostname: local.localHostname, + environment: local.environment, + executable: local.executable, + resolvedExecutable: local.resolvedExecutable, + script: local.script, + executableArguments: local.executableArguments, + packageConfig: local.packageConfig, + version: local.version, + stdinSupportsAnsi: local.stdinSupportsAnsi, + stdoutSupportsAnsi: local.stdoutSupportsAnsi, + localeName: local.localeName, + ); + _expectPlatformsEqual(copy, local); + }); + }); + + group('json', () { + test('fromJson', () { + final json = io.File('test/platform.json').readAsStringSync(); + fake = FakePlatform.fromJson(json); + expect(fake.numberOfProcessors, 8); + expect(fake.pathSeparator, '/'); + expect(fake.operatingSystem, 'macos'); + expect(fake.operatingSystemVersion, '10.14.5'); + expect(fake.localHostname, 'platform.test.org'); + expect(fake.environment, { + 'PATH': '/bin', + 'PWD': '/platform', + }); + expect(fake.executable, '/bin/dart'); + expect(fake.resolvedExecutable, '/bin/dart'); + expect(fake.script, Uri.file('/platform/test/fake_platform_test.dart')); + expect(fake.executableArguments, ['--checked']); + expect(fake.packageConfig, null); + expect(fake.version, '1.22.0'); + expect(fake.localeName, 'de/de'); + }); + + test('fromJsonToJson', () { + fake = FakePlatform.fromJson(local.toJson()); + _expectPlatformsEqual(fake, local); + }); + }); + }); + + test('Throws when unset non-null values are read', () { + final platform = FakePlatform(); + + expect(() => platform.numberOfProcessors, throwsA(isStateError)); + expect(() => platform.pathSeparator, throwsA(isStateError)); + expect(() => platform.operatingSystem, throwsA(isStateError)); + expect(() => platform.operatingSystemVersion, throwsA(isStateError)); + expect(() => platform.localHostname, throwsA(isStateError)); + expect(() => platform.environment, throwsA(isStateError)); + expect(() => platform.executable, throwsA(isStateError)); + expect(() => platform.resolvedExecutable, throwsA(isStateError)); + expect(() => platform.script, throwsA(isStateError)); + expect(() => platform.executableArguments, throwsA(isStateError)); + expect(() => platform.version, throwsA(isStateError)); + expect(() => platform.localeName, throwsA(isStateError)); + }); +} diff --git a/pkgs/platform/test/platform.json b/pkgs/platform/test/platform.json new file mode 100644 index 000000000..60b7c139d --- /dev/null +++ b/pkgs/platform/test/platform.json @@ -0,0 +1,20 @@ +{ + "numberOfProcessors": 8, + "pathSeparator": "/", + "operatingSystem": "macos", + "operatingSystemVersion": "10.14.5", + "localHostname": "platform.test.org", + "environment": { + "PATH": "/bin", + "PWD": "/platform" + }, + "executable": "/bin/dart", + "resolvedExecutable": "/bin/dart", + "script": "file:///platform/test/fake_platform_test.dart", + "executableArguments": [ + "--checked" + ], + "packageConfig": null, + "version": "1.22.0", + "localeName": "de/de" +} \ No newline at end of file