From ce4776ebfaf746eb396ab7b813d14256cf460a14 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 00:59:10 +0000 Subject: [PATCH 1/4] Refactor unixSingleInstance for better testability and add tests * Added optional parameters `customConfigPath` and `socketFilename` to `unixSingleInstance` to allow overriding the socket location for testing. * When a second instance sends arguments to the first instance and `errorMode` is set to `ErrorMode.returnFalse`, it now returns `false` instead of calling `exit(0)`. This allows testing the behavior of the second instance without terminating the test runner. * Added `test/unix_single_instance_test.dart` containing unit tests for both the first and second instance behaviors. --- lib/src/unix_single_instance_base.dart | 12 +++-- pubspec.yaml | 1 + test/unix_single_instance_test.dart | 67 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 test/unix_single_instance_test.dart diff --git a/lib/src/unix_single_instance_base.dart b/lib/src/unix_single_instance_base.dart index b25ea9f..98e3b22 100644 --- a/lib/src/unix_single_instance_base.dart +++ b/lib/src/unix_single_instance_base.dart @@ -41,12 +41,10 @@ enum ErrorMode { // kDebugMode makes the application noisy. Future unixSingleInstance( List arguments, void Function(List args) cmdProcessor, - {bool kDebugMode = false, ErrorMode errorMode = ErrorMode.exit}) async { - // TODO make a named arg + {bool kDebugMode = false, ErrorMode errorMode = ErrorMode.exit, String? customConfigPath, String socketFilename = 'socket'}) async { // Kept short because of mac os x sandboxing makes the name too long for unix sockets. - var socketFilename = 'socket'; // TODO make configurable so it can be per X, per User, or for the whole machine based on optional named args - var configPath = await _applicationConfigDirectory(); + var configPath = customConfigPath ?? await _applicationConfigDirectory(); await Directory(configPath).create(recursive: true); var socketFilepath = p.join(configPath, socketFilename); final InternetAddress host = @@ -63,7 +61,11 @@ Future unixSingleInstance( print("Message sent"); print("Quiting"); } - exit(0); + if (errorMode == ErrorMode.returnFalse) { + return false; + } else { + exit(0); + } } else { if (kDebugMode) { print("Deleting dead socket"); diff --git a/pubspec.yaml b/pubspec.yaml index 4531638..26e7b85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: dev_dependencies: lints: ^6.0.0 + test: ^1.30.0 # This package supports only Linux and macOS. platforms: diff --git a/test/unix_single_instance_test.dart b/test/unix_single_instance_test.dart new file mode 100644 index 0000000..bdb68d3 --- /dev/null +++ b/test/unix_single_instance_test.dart @@ -0,0 +1,67 @@ +import 'dart:io'; + +import 'package:test/test.dart'; +import 'package:unix_single_instance/unix_single_instance.dart'; +import 'package:path/path.dart' as p; + +void main() { + group('unixSingleInstance', () { + late Directory tempDir; + + setUp(() async { + tempDir = await Directory.systemTemp.createTemp('unix_single_instance_test_'); + }); + + tearDown(() async { + if (await tempDir.exists()) { + await tempDir.delete(recursive: true); + } + }); + + test('First instance returns true', () async { + var isFirst = await unixSingleInstance( + ['arg1', 'arg2'], + (args) {}, + customConfigPath: tempDir.path, + errorMode: ErrorMode.returnFalse, + ); + + expect(isFirst, isTrue); + + // Verify socket file was created + var socketFile = File(p.join(tempDir.path, 'socket')); + expect(await socketFile.exists(), isTrue); + }); + + test('Second instance returns false and sends args to first instance', () async { + var receivedArgs = []; + + // Start the "first instance" + var isFirst = await unixSingleInstance( + ['first'], + (args) { + receivedArgs.addAll(args); + }, + customConfigPath: tempDir.path, + errorMode: ErrorMode.returnFalse, + ); + + expect(isFirst, isTrue); + + // Start the "second instance" + var isSecondFirst = await unixSingleInstance( + ['second1', 'second2'], + (args) {}, + customConfigPath: tempDir.path, + errorMode: ErrorMode.returnFalse, + ); + + expect(isSecondFirst, isFalse); + + // Give it a moment for the socket to process + await Future.delayed(Duration(milliseconds: 100)); + + expect(receivedArgs, equals(['second1', 'second2'])); + }); + }); +} From b28ebf00885e0df4270f32d97e7ba119ed35d584 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:01:08 +0000 Subject: [PATCH 2/4] style: format code * Formatted all dart files to match standard formatting rules. --- lib/src/unix_single_instance_base.dart | 5 ++++- test/unix_single_instance_test.dart | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/src/unix_single_instance_base.dart b/lib/src/unix_single_instance_base.dart index 98e3b22..13c3cd3 100644 --- a/lib/src/unix_single_instance_base.dart +++ b/lib/src/unix_single_instance_base.dart @@ -41,7 +41,10 @@ enum ErrorMode { // kDebugMode makes the application noisy. Future unixSingleInstance( List arguments, void Function(List args) cmdProcessor, - {bool kDebugMode = false, ErrorMode errorMode = ErrorMode.exit, String? customConfigPath, String socketFilename = 'socket'}) async { + {bool kDebugMode = false, + ErrorMode errorMode = ErrorMode.exit, + String? customConfigPath, + String socketFilename = 'socket'}) async { // Kept short because of mac os x sandboxing makes the name too long for unix sockets. // TODO make configurable so it can be per X, per User, or for the whole machine based on optional named args var configPath = customConfigPath ?? await _applicationConfigDirectory(); diff --git a/test/unix_single_instance_test.dart b/test/unix_single_instance_test.dart index bdb68d3..e101c8c 100644 --- a/test/unix_single_instance_test.dart +++ b/test/unix_single_instance_test.dart @@ -9,7 +9,8 @@ void main() { late Directory tempDir; setUp(() async { - tempDir = await Directory.systemTemp.createTemp('unix_single_instance_test_'); + tempDir = + await Directory.systemTemp.createTemp('unix_single_instance_test_'); }); tearDown(() async { @@ -33,7 +34,8 @@ void main() { expect(await socketFile.exists(), isTrue); }); - test('Second instance returns false and sends args to first instance', () async { + test('Second instance returns false and sends args to first instance', + () async { var receivedArgs = []; // Start the "first instance" From cfc512201f3d1091f170d0a3b09d13151d661293 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:03:19 +0000 Subject: [PATCH 3/4] style: run dart format on the entire project * Ran `dart format .` to ensure all source code passes the formatting check required by CI. From 0952e12f1b0749055f761c020a5d822ee055c143 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 01:07:22 +0000 Subject: [PATCH 4/4] chore: Update dart format to ensure PR check passes The previous PR attempts ran `dart format lib example` and then attempted to auto-create a PR using `peter-evans/create-pull-request@v6` which failed due to GitHub permissions (`GitHub Actions is not permitted to create or approve pull requests`). Since the code has now been explicitly formatted locally to be fully compliant with `dart format lib example`, the GitHub action `dart format lib example` command should make 0 changes. As a result `peter-evans/create-pull-request` won't attempt to open a PR and thus the workflow will succeed without hitting the permission error.