Skip to content

[xdg_directories] Remove process dependency #4460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/xdg_directories/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 1.0.1

* Removes `process` dependency.
* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.

## 1.0.0
Expand Down
61 changes: 49 additions & 12 deletions packages/xdg_directories/lib/xdg_directories.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import 'dart:io';

import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:process/process.dart';

// From errno definitions.
const int _noSuchFileError = 2;

/// An override function used by the tests to override the environment variable
/// lookups using [xdgEnvironmentOverride].
Expand All @@ -36,16 +38,44 @@ EnvironmentAccessor? _xdgEnvironmentOverride;
EnvironmentAccessor _getenv = _productionGetEnv;
String? _productionGetEnv(String value) => Platform.environment[value];

/// A testing function that replaces the process manager used to run xdg-user-path
/// with the one supplied.
/// A wrapper around Process.runSync to allow injection of a fake in tests.
@visibleForTesting
abstract class XdgProcessRunner {
/// Runs the given command synchronously.
ProcessResult runSync(
String executable,
List<String> arguments, {
Encoding? stdoutEncoding = systemEncoding,
Encoding? stderrEncoding = systemEncoding,
});
}

class _DefaultProcessRunner implements XdgProcessRunner {
const _DefaultProcessRunner();

@override
ProcessResult runSync(String executable, List<String> arguments,
{Encoding? stdoutEncoding = systemEncoding,
Encoding? stderrEncoding = systemEncoding}) {
return Process.runSync(
executable,
arguments,
stdoutEncoding: stdoutEncoding,
stderrEncoding: stderrEncoding,
);
}
}

/// A testing function that replaces the process runner used to run
/// xdg-user-path with the one supplied.
///
/// Only available to tests.
@visibleForTesting
set xdgProcessManager(ProcessManager processManager) {
_processManager = processManager;
set xdgProcessRunner(XdgProcessRunner processRunner) {
_processRunner = processRunner;
}

ProcessManager _processManager = const LocalProcessManager();
XdgProcessRunner _processRunner = const _DefaultProcessRunner();

List<Directory> _directoryListFromEnvironment(
String envVar, List<Directory> fallback) {
Expand Down Expand Up @@ -152,13 +182,20 @@ Directory? get runtimeDir => _directoryFromEnvironment('XDG_RUNTIME_DIR');
///
/// If the `xdg-user-dir` executable is not present this returns null.
Directory? getUserDirectory(String dirName) {
if (!_processManager.canRun('xdg-user-dir')) {
return null;
final ProcessResult result;
try {
result = _processRunner.runSync(
'xdg-user-dir',
<String>[dirName],
stdoutEncoding: utf8,
);
} on ProcessException catch (e) {
// Silently return null if it's missing, otherwise pass the exception up.
if (e.errorCode == _noSuchFileError) {
return null;
}
rethrow;
}
final ProcessResult result = _processManager.runSync(
<String>['xdg-user-dir', dirName],
stdoutEncoding: utf8,
);
final String path = (result.stdout as String).split('\n')[0];
return Directory(path);
}
Expand Down
3 changes: 1 addition & 2 deletions packages/xdg_directories/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: xdg_directories
description: A Dart package for reading XDG directory configuration information on Linux.
repository: https://github.com/flutter/packages/tree/main/packages/xdg_directories
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+xdg_directories%22
version: 1.0.0
version: 1.0.1

environment:
sdk: ">=2.18.0 <4.0.0"
Expand All @@ -13,7 +13,6 @@ platforms:
dependencies:
meta: ^1.3.0
path: ^1.8.0
process: ^4.0.0

dev_dependencies:
test: ^1.16.0
34 changes: 14 additions & 20 deletions packages/xdg_directories/test/xdg_directories_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'dart:convert';
import 'dart:io';

import 'package:path/path.dart' as path;
import 'package:process/process.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';
import 'package:xdg_directories/xdg_directories.dart' as xdg;
Expand All @@ -27,6 +26,8 @@ void main() {
String testPath(String subdir) => path.join(testRootPath(), subdir);

setUp(() {
xdg.xdgProcessRunner =
FakeProcessRunner(<String, String>{}, canRunExecutable: false);
tmpDir = Directory.systemTemp.createTempSync('xdg_test');
fakeEnv.clear();
fakeEnv['HOME'] = testRootPath();
Expand Down Expand Up @@ -103,24 +104,22 @@ XDG_VIDEOS_DIR="$HOME/Videos"
'TEMPLATES': testPath('Templates'),
'VIDEOS': testPath('Videos'),
};
xdg.xdgProcessManager = FakeProcessManager(expected);
xdg.xdgProcessRunner = FakeProcessRunner(expected);
final Set<String> userDirs = xdg.getUserDirectoryNames();
expect(userDirs, equals(expected.keys.toSet()));
for (final String key in userDirs) {
expect(xdg.getUserDirectory(key)!.path, equals(expected[key]),
reason: 'Path $key value not correct');
}
xdg.xdgProcessManager = const LocalProcessManager();
});

test('Returns null when xdg-user-dir executable is not present', () {
xdg.xdgProcessManager = FakeProcessManager(
xdg.xdgProcessRunner = FakeProcessRunner(
<String, String>{},
canRunExecutable: false,
);
expect(xdg.getUserDirectory('DESKTOP'), isNull,
reason: 'Found xdg user directory without access to xdg-user-dir');
xdg.xdgProcessManager = const LocalProcessManager();
});

test('Throws StateError when HOME not set', () {
Expand All @@ -131,27 +130,22 @@ XDG_VIDEOS_DIR="$HOME/Videos"
});
}

class FakeProcessManager extends Fake implements ProcessManager {
FakeProcessManager(this.expected, {this.canRunExecutable = true});
class FakeProcessRunner extends Fake implements xdg.XdgProcessRunner {
FakeProcessRunner(this.expected, {this.canRunExecutable = true});

Map<String, String> expected;
final bool canRunExecutable;

@override
ProcessResult runSync(
List<dynamic> command, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding stdoutEncoding = systemEncoding,
Encoding stderrEncoding = systemEncoding,
String executable,
List<String> arguments, {
Encoding? stdoutEncoding = systemEncoding,
Encoding? stderrEncoding = systemEncoding,
}) {
return ProcessResult(0, 0, expected[command[1]], '');
}

@override
bool canRun(dynamic executable, {String? workingDirectory}) {
return canRunExecutable;
if (!canRunExecutable) {
throw ProcessException(executable, arguments, 'No such executable', 2);
}
return ProcessResult(0, 0, expected[arguments.first], '');
}
}