From a329bb03a9fca4fa741c9b9ca663dbb74594e1f8 Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Tue, 27 Aug 2024 05:19:05 +0100 Subject: [PATCH 1/4] Add broadcast option to test command --- .../java/maestro/cli/command/TestCommand.kt | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt index e0da714194..0186a4ef5e 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt @@ -86,6 +86,12 @@ class TestCommand : Callable { ) private var shards: Int = 1 + @Option( + names = ["-b", "--broadcast"], + description = ["Broadcasts all the tests across all running devices"] + ) + private var broadcast: Boolean = false + @Option(names = ["-c", "--continuous"]) private var continuous: Boolean = false @@ -178,7 +184,6 @@ class TestCommand : Callable { } private fun handleSessions(debugOutputPath: Path, plan: ExecutionPlan): Int = runBlocking(Dispatchers.IO) { - val sharded = shards > 1 runCatching { val deviceIds = (if (isWebFlow()) @@ -194,10 +199,16 @@ class TestCommand : Callable { initialActiveDevices.addAll(DeviceService.listConnectedDevices().map { it.instanceId }.toMutableSet()) - val effectiveShards = shards.coerceAtMost(plan.flowsToRun.size) - val chunkPlans = plan.flowsToRun + + val availableDevices = if (deviceIds.isNotEmpty()) deviceIds.size else initialActiveDevices.size + val effectiveShards = if (broadcast) availableDevices else shards.coerceAtMost(plan.flowsToRun.size) + val sharded = effectiveShards > 1 + + val chunkPlans = + if (broadcast) (0 until availableDevices).map { ExecutionPlan(plan.flowsToRun, plan.sequence) } + else plan.flowsToRun .withIndex() - .groupBy { it.index % shards } + .groupBy { it.index % effectiveShards } .map { (shardIndex, files) -> ExecutionPlan( files.map { it.value }, @@ -209,12 +220,12 @@ class TestCommand : Callable { } // Collect device configurations for missing shards, if any - val missing = effectiveShards - if (deviceIds.isNotEmpty()) deviceIds.size else initialActiveDevices.size - val allDeviceConfigs = (0 until missing).map { shardIndex -> + val missing = effectiveShards - availableDevices + val allDeviceConfigs = if (!broadcast) (0 until missing).map { shardIndex -> PrintUtils.message("------------------ Shard ${shardIndex + 1} ------------------") // Collect device configurations here, one per shard PickDeviceView.requestDeviceOptions() - }.toMutableList() + }.toMutableList() else mutableListOf() val barrier = CountDownLatch(effectiveShards) From fadf7a732b55cd6239f2902110338cf1192b6b70 Mon Sep 17 00:00:00 2001 From: Tarek Belkahia Date: Sat, 31 Aug 2024 16:41:40 +0100 Subject: [PATCH 2/4] Rename broadcast to replicate --- .../src/main/java/maestro/cli/command/TestCommand.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt index 0186a4ef5e..d79ecea359 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt @@ -87,10 +87,10 @@ class TestCommand : Callable { private var shards: Int = 1 @Option( - names = ["-b", "--broadcast"], - description = ["Broadcasts all the tests across all running devices"] + names = ["-r", "--replicate"], + description = ["Replicates all the tests across all running devices"] ) - private var broadcast: Boolean = false + private var replicate: Boolean = false @Option(names = ["-c", "--continuous"]) private var continuous: Boolean = false @@ -201,11 +201,11 @@ class TestCommand : Callable { }.toMutableSet()) val availableDevices = if (deviceIds.isNotEmpty()) deviceIds.size else initialActiveDevices.size - val effectiveShards = if (broadcast) availableDevices else shards.coerceAtMost(plan.flowsToRun.size) + val effectiveShards = if (replicate) availableDevices else shards.coerceAtMost(plan.flowsToRun.size) val sharded = effectiveShards > 1 val chunkPlans = - if (broadcast) (0 until availableDevices).map { ExecutionPlan(plan.flowsToRun, plan.sequence) } + if (replicate) (0 until availableDevices).map { ExecutionPlan(plan.flowsToRun, plan.sequence) } else plan.flowsToRun .withIndex() .groupBy { it.index % effectiveShards } @@ -221,7 +221,7 @@ class TestCommand : Callable { // Collect device configurations for missing shards, if any val missing = effectiveShards - availableDevices - val allDeviceConfigs = if (!broadcast) (0 until missing).map { shardIndex -> + val allDeviceConfigs = if (!replicate) (0 until missing).map { shardIndex -> PrintUtils.message("------------------ Shard ${shardIndex + 1} ------------------") // Collect device configurations here, one per shard PickDeviceView.requestDeviceOptions() From cf0766493fc12a1188d629acc68d86416bf5318f Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Tue, 3 Sep 2024 10:57:46 +0200 Subject: [PATCH 3/4] use `ExecutionPLan.copy()` --- maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt index d79ecea359..be245e1315 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt @@ -205,7 +205,7 @@ class TestCommand : Callable { val sharded = effectiveShards > 1 val chunkPlans = - if (replicate) (0 until availableDevices).map { ExecutionPlan(plan.flowsToRun, plan.sequence) } + if (replicate) (0 until availableDevices).map { plan.copy() } else plan.flowsToRun .withIndex() .groupBy { it.index % effectiveShards } From 14b27d0c9a89e02e5b947900acffb848020ccc36 Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Tue, 3 Sep 2024 12:12:09 +0200 Subject: [PATCH 4/4] improve naming - Deprecate `--shards` - Introduce `--shard-split` and `--shard-all` --- .../java/maestro/cli/command/TestCommand.kt | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt index be245e1315..8899d3d999 100644 --- a/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt +++ b/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt @@ -82,15 +82,22 @@ class TestCommand : Callable { @Option( names = ["-s", "--shards"], - description = ["Number of parallel shards to distribute tests across"] + description = ["Number of parallel shards to distribute tests across"], ) - private var shards: Int = 1 + @Deprecated("Use --shard-split or --shard-all instead") + private var legacyShardCount: Int? = null @Option( - names = ["-r", "--replicate"], - description = ["Replicates all the tests across all running devices"] + names = ["--shard-split"], + description = ["Splits the tests across N connected devices"], ) - private var replicate: Boolean = false + private var shardSplit: Int? = null + + @Option( + names = ["--shard-all"], + description = ["Replicates all the tests across N connected devices"], + ) + private var shardAll: Int? = null @Option(names = ["-c", "--continuous"]) private var continuous: Boolean = false @@ -161,6 +168,15 @@ class TestCommand : Callable { throw CliError("--platform option was deprecated. You can remove it to run your test.") } + if (shardSplit != null && shardAll != null) { + throw CliError("Options --shard-split and --shard-all are mutually exclusive.") + } + + if (legacyShardCount != null) { + PrintUtils.warn("--shards option is deprecated and will be removed in the next Maestro version. Use --shard-split or --shard-all instead.") + shardSplit = legacyShardCount + } + val executionPlan = try { WorkspaceExecutionPlanner.plan( flowFile.toPath().toAbsolutePath(), @@ -200,12 +216,14 @@ class TestCommand : Callable { it.instanceId }.toMutableSet()) + val shards = shardSplit ?: shardAll ?: 1 + val availableDevices = if (deviceIds.isNotEmpty()) deviceIds.size else initialActiveDevices.size - val effectiveShards = if (replicate) availableDevices else shards.coerceAtMost(plan.flowsToRun.size) + val effectiveShards = shards.coerceAtMost(plan.flowsToRun.size) val sharded = effectiveShards > 1 val chunkPlans = - if (replicate) (0 until availableDevices).map { plan.copy() } + if (shardAll != null) (0 until effectiveShards).map { plan.copy() } else plan.flowsToRun .withIndex() .groupBy { it.index % effectiveShards } @@ -221,7 +239,7 @@ class TestCommand : Callable { // Collect device configurations for missing shards, if any val missing = effectiveShards - availableDevices - val allDeviceConfigs = if (!replicate) (0 until missing).map { shardIndex -> + val allDeviceConfigs = if (shardAll == null) (0 until missing).map { shardIndex -> PrintUtils.message("------------------ Shard ${shardIndex + 1} ------------------") // Collect device configurations here, one per shard PickDeviceView.requestDeviceOptions()