@@ -83,9 +83,22 @@ class TestCommand : Callable<Int> {
83
83
84
84
@Option(
85
85
names = [" -s" , " --shards" ],
86
- description = [" Number of parallel shards to distribute tests across" ]
86
+ description = [" Number of parallel shards to distribute tests across" ],
87
87
)
88
- private var shards: Int = 1
88
+ @Deprecated(" Use --shard-split or --shard-all instead" )
89
+ private var legacyShardCount: Int? = null
90
+
91
+ @Option(
92
+ names = [" --shard-split" ],
93
+ description = [" Splits the tests across N connected devices" ],
94
+ )
95
+ private var shardSplit: Int? = null
96
+
97
+ @Option(
98
+ names = [" --shard-all" ],
99
+ description = [" Replicates all the tests across N connected devices" ],
100
+ )
101
+ private var shardAll: Int? = null
89
102
90
103
@Option(names = [" -c" , " --continuous" ])
91
104
private var continuous: Boolean = false
@@ -152,6 +165,14 @@ class TestCommand : Callable<Int> {
152
165
}
153
166
154
167
override fun call (): Int {
168
+ if (shardSplit != null && shardAll != null ) {
169
+ throw CliError (" Options --shard-split and --shard-all are mutually exclusive." )
170
+ }
171
+
172
+ if (legacyShardCount != null ) {
173
+ PrintUtils .warn(" --shards option is deprecated and will be removed in the next Maestro version. Use --shard-split or --shard-all instead." )
174
+ shardSplit = legacyShardCount
175
+ }
155
176
val executionPlan = try {
156
177
WorkspaceExecutionPlanner .plan(
157
178
flowFile.toPath().toAbsolutePath(),
@@ -177,7 +198,6 @@ class TestCommand : Callable<Int> {
177
198
}
178
199
179
200
private fun handleSessions (debugOutputPath : Path , plan : ExecutionPlan ): Int = runBlocking(Dispatchers .IO ) {
180
- val sharded = shards > 1
181
201
182
202
runCatching {
183
203
val deviceIds = (if (isWebFlow())
@@ -193,10 +213,18 @@ class TestCommand : Callable<Int> {
193
213
initialActiveDevices.addAll(DeviceService .listConnectedDevices().map {
194
214
it.instanceId
195
215
}.toMutableSet())
216
+
217
+ val shards = shardSplit ? : shardAll ? : 1
218
+
219
+ val availableDevices = if (deviceIds.isNotEmpty()) deviceIds.size else initialActiveDevices.size
196
220
val effectiveShards = shards.coerceAtMost(plan.flowsToRun.size)
197
- val chunkPlans = plan.flowsToRun
221
+ val sharded = effectiveShards > 1
222
+
223
+ val chunkPlans =
224
+ if (shardAll != null ) (0 until effectiveShards).map { plan.copy() }
225
+ else plan.flowsToRun
198
226
.withIndex()
199
- .groupBy { it.index % shards }
227
+ .groupBy { it.index % effectiveShards }
200
228
.map { (shardIndex, files) ->
201
229
ExecutionPlan (
202
230
files.map { it.value },
@@ -208,12 +236,12 @@ class TestCommand : Callable<Int> {
208
236
}
209
237
210
238
// Collect device configurations for missing shards, if any
211
- val missing = effectiveShards - if (deviceIds.isNotEmpty()) deviceIds.size else initialActiveDevices.size
212
- val allDeviceConfigs = (0 until missing).map { shardIndex ->
239
+ val missing = effectiveShards - availableDevices
240
+ val allDeviceConfigs = if (shardAll == null ) (0 until missing).map { shardIndex ->
213
241
PrintUtils .message(" ------------------ Shard ${shardIndex + 1 } ------------------" )
214
242
// Collect device configurations here, one per shard
215
243
PickDeviceView .requestDeviceOptions()
216
- }.toMutableList()
244
+ }.toMutableList() else mutableListOf ()
217
245
218
246
val barrier = CountDownLatch (effectiveShards)
219
247
0 commit comments