Skip to content

Commit d22509d

Browse files
committed
Refactor run configuration for flexible argument passing
Based on infection-plugin architecture: - TestoRunnerSettings: add @Tag/@Attribute annotations for XMLB serialization, new fields: command, suite, group, excludeGroup, repeat, parallel. fromPhpTestRunnerSettings() now copies Testo-specific fields. - TestoRunConfigurationHandler: add prepareArguments() that builds argument list from settings (suite, group, excludeGroup, repeat, parallel). Overloaded prepareCommand() supports custom subcommand. Extract parseMethodName() for method#dataProvider parsing. - TestoRunConfiguration: override createCommand() to take full control of command assembly. Extract fillTestRunnerArguments() for scope-based args (Type, Directory, File, Method, ConfigurationFile). - TestoRunConfigurationSettings: add @Property/@transient for proper XMLB serialization of TestoRunnerSettings. - TestoTestRunConfigurationEditor: add UI fields for command, suite, group, excludeGroup, repeat, parallel with proper reset/apply binding and reflection-based parent editor integration. - Tests: full coverage for prepareArguments(), parseMethodName(), fromPhpTestRunnerSettings() with new fields. https://claude.ai/code/session_01TmFvjfMtoxTvddQRBzsbPN
1 parent 93bb124 commit d22509d

7 files changed

Lines changed: 553 additions & 43 deletions

File tree

src/main/kotlin/com/github/xepozz/testo/tests/run/TestoRunConfiguration.kt

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,30 @@ import com.github.xepozz.testo.isTestoExecutable
55
import com.github.xepozz.testo.tests.TestoConsoleProperties
66
import com.github.xepozz.testo.tests.TestoFrameworkType
77
import com.github.xepozz.testo.tests.actions.TestoRerunFailedTestsAction
8+
import com.intellij.execution.ExecutionException
89
import com.intellij.execution.Executor
910
import com.intellij.execution.configurations.ConfigurationFactory
11+
import com.intellij.execution.configurations.ParametersList
1012
import com.intellij.execution.configurations.RunConfiguration
1113
import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties
1214
import com.intellij.execution.ui.ConsoleView
1315
import com.intellij.openapi.options.SettingsEditor
1416
import com.intellij.openapi.project.Project
17+
import com.intellij.openapi.util.text.StringUtil
18+
import com.jetbrains.php.PhpBundle
1519
import com.jetbrains.php.config.commandLine.PhpCommandLinePathProcessor
20+
import com.jetbrains.php.config.commandLine.PhpCommandSettings
21+
import com.jetbrains.php.config.commandLine.PhpCommandSettingsBuilder
22+
import com.jetbrains.php.config.interpreters.PhpInterpreter
1623
import com.jetbrains.php.run.PhpAsyncRunConfiguration
1724
import com.jetbrains.php.run.remote.PhpRemoteInterpreterManager
1825
import com.jetbrains.php.testFramework.PhpTestFrameworkConfiguration
1926
import com.jetbrains.php.testFramework.run.PhpTestRunConfiguration
2027
import com.jetbrains.php.testFramework.run.PhpTestRunConfigurationEditor
28+
import com.jetbrains.php.testFramework.run.PhpTestRunConfigurationHandler
2129
import com.jetbrains.php.testFramework.run.PhpTestRunConfigurationSettings
2230
import com.jetbrains.php.testFramework.run.PhpTestRunnerConfigurationEditor
31+
import com.jetbrains.php.testFramework.run.PhpTestRunnerSettings
2332

2433
class TestoRunConfiguration(project: Project, factory: ConfigurationFactory) : PhpTestRunConfiguration(
2534
project,
@@ -29,6 +38,8 @@ class TestoRunConfiguration(project: Project, factory: ConfigurationFactory) : P
2938
TestoTestRunnerSettingsValidator,
3039
TestoRunConfigurationHandler.INSTANCE,
3140
), PhpAsyncRunConfiguration {
41+
val myHandler = TestoRunConfigurationHandler.INSTANCE
42+
3243
val testoSettings
3344
get() = settings as TestoRunConfigurationSettings
3445

@@ -46,9 +57,8 @@ class TestoRunConfiguration(project: Project, factory: ConfigurationFactory) : P
4657

4758
override fun getConfigurationEditor(): SettingsEditor<out RunConfiguration> {
4859
val editor = super.getConfigurationEditor() as PhpTestRunConfigurationEditor
49-
editor.setRunnerOptionsDocumentation("https://infection.github.io/guide/command-line-options.html")
60+
editor.setRunnerOptionsDocumentation("https://github.com/testo/testo")
5061

51-
// return this.addExtensionEditor(editor)!!
5262
return TestoTestRunConfigurationEditor(editor, this)
5363
}
5464

@@ -58,8 +68,54 @@ class TestoRunConfiguration(project: Project, factory: ConfigurationFactory) : P
5868
config: PhpTestFrameworkConfiguration?
5969
) = project.basePath
6070

71+
override fun createCommand(
72+
interpreter: PhpInterpreter,
73+
env: MutableMap<String?, String?>,
74+
arguments: MutableList<String?>,
75+
frameworkConfig: PhpTestFrameworkConfiguration?,
76+
withDebugger: Boolean
77+
): PhpCommandSettings {
78+
val command = PhpCommandSettingsBuilder(project, interpreter)
79+
.loadAndStartDebug(withDebugger)
80+
.build()
81+
82+
val executablePath = frameworkConfig?.executablePath
83+
if (frameworkConfig == null || executablePath.isNullOrEmpty()) {
84+
throw ExecutionException(
85+
PhpBundle.message(
86+
"php.interpreter.base.configuration.is.not.provided.or.empty",
87+
frameworkName,
88+
if (command.isRemote) "'${interpreter.name}' interpreter" else "local machine",
89+
)
90+
)
91+
}
92+
93+
val workingDirectory = getWorkingDirectory(project, settings, frameworkConfig)
94+
if (workingDirectory.isNullOrEmpty()) {
95+
throw ExecutionException(PhpBundle.message("php.interpreter.base.configuration.working.directory"))
96+
}
97+
command.setWorkingDir(workingDirectory)
98+
99+
myHandler.prepareArguments(arguments, testoSettings)
100+
myHandler.prepareCommand(project, command, executablePath, null, testoSettings.runnerSettings.command)
101+
102+
command.importCommandLineSettings(settings.commandLineSettings, workingDirectory)
103+
command.addEnvs(env)
104+
105+
fillTestRunnerArguments(
106+
project,
107+
workingDirectory,
108+
settings.runnerSettings,
109+
arguments,
110+
command,
111+
frameworkConfig,
112+
myHandler,
113+
)
114+
115+
return command
116+
}
117+
61118
override fun createTestConsoleProperties(executor: Executor): SMTRunnerConsoleProperties {
62-
// println("createTestConsoleProperties")
63119
val manager = PhpRemoteInterpreterManager.getInstance()
64120

65121
val pathProcessor = when (this.interpreter?.isRemote) {
@@ -76,8 +132,60 @@ class TestoRunConfiguration(project: Project, factory: ConfigurationFactory) : P
76132
}
77133

78134
companion object Companion {
79-
const val ID = "InfectionConsoleCommandRunConfiguration"
135+
const val ID = "TestoConsoleCommandRunConfiguration"
136+
137+
private fun fillTestRunnerArguments(
138+
project: Project,
139+
workingDirectory: String,
140+
testRunnerSettings: PhpTestRunnerSettings,
141+
arguments: MutableList<String?>,
142+
command: PhpCommandSettings,
143+
configuration: PhpTestFrameworkConfiguration?,
144+
handler: PhpTestRunConfigurationHandler,
145+
) {
146+
val testRunnerOptions = testRunnerSettings.testRunnerOptions
147+
if (StringUtil.isNotEmpty(testRunnerOptions)) {
148+
command.addArguments(ParametersList.parse(testRunnerOptions!!).toList())
149+
}
150+
151+
command.addArguments(arguments)
152+
153+
val configurationFilePath = getConfigurationFile(testRunnerSettings, configuration)
154+
if (!configurationFilePath.isNullOrEmpty()) {
155+
command.addArgument(handler.configFileOption)
156+
command.addPathArgument(configurationFilePath)
157+
}
158+
159+
val scope = testRunnerSettings.scope
160+
when (scope) {
161+
PhpTestRunnerSettings.Scope.Type -> handler.runType(
162+
project,
163+
command,
164+
StringUtil.notNullize(testRunnerSettings.selectedType),
165+
workingDirectory,
166+
)
167+
168+
PhpTestRunnerSettings.Scope.Directory -> handler.runDirectory(
169+
project,
170+
command,
171+
StringUtil.notNullize(testRunnerSettings.directoryPath),
172+
workingDirectory,
173+
)
174+
175+
PhpTestRunnerSettings.Scope.File -> handler.runFile(
176+
project,
177+
command,
178+
StringUtil.notNullize(testRunnerSettings.filePath),
179+
workingDirectory,
180+
)
181+
182+
PhpTestRunnerSettings.Scope.Method -> {
183+
val filePath = StringUtil.notNullize(testRunnerSettings.filePath)
184+
handler.runMethod(project, command, filePath, testRunnerSettings.methodName, workingDirectory)
185+
}
80186

81-
// val INSTANCE = TestoRunConfiguration()
187+
PhpTestRunnerSettings.Scope.ConfigurationFile -> {}
188+
}
189+
}
82190
}
83-
}
191+
}

src/main/kotlin/com/github/xepozz/testo/tests/run/TestoRunConfigurationHandler.kt

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,45 @@ class TestoRunConfigurationHandler : PhpTestRunConfigurationHandler {
1313
override fun getConfigFileOption() = "--config"
1414

1515
override fun prepareCommand(project: Project, commandSettings: PhpCommandSettings, exe: String, version: String?) {
16+
prepareCommand(project, commandSettings, exe, version, "run")
17+
}
18+
19+
fun prepareCommand(
20+
project: Project,
21+
commandSettings: PhpCommandSettings,
22+
exe: String,
23+
version: String?,
24+
command: String,
25+
) {
1626
commandSettings.apply {
1727
setScript(exe, true)
18-
addArgument("run")
19-
// addArgument("--no-progress")
20-
// addArgument("-n")
21-
// addArgument("-q")
22-
// addArgument("--logger-gitlab=php://stdout")
28+
addArgument(command)
29+
}
30+
}
31+
32+
fun prepareArguments(arguments: MutableList<String?>, testoSettings: TestoRunConfigurationSettings) {
33+
val runner = testoSettings.runnerSettings
34+
35+
if (runner.suite.isNotEmpty()) {
36+
arguments.add("--suite")
37+
arguments.add(runner.suite)
38+
}
39+
if (runner.group.isNotEmpty()) {
40+
arguments.add("--group")
41+
arguments.add(runner.group)
42+
}
43+
if (runner.excludeGroup.isNotEmpty()) {
44+
arguments.add("--exclude-group")
45+
arguments.add(runner.excludeGroup)
46+
}
47+
if (runner.repeat > 0) {
48+
arguments.add("--repeat")
49+
arguments.add(runner.repeat.toString())
50+
}
51+
if (runner.parallel > 0) {
52+
arguments.add("--parallel")
53+
arguments.add(runner.parallel.toString())
2354
}
24-
// println("commandSettings: $commandSettings")
2555
}
2656

2757
override fun runType(
@@ -30,8 +60,6 @@ class TestoRunConfigurationHandler : PhpTestRunConfigurationHandler {
3060
type: String,
3161
workingDirectory: String
3262
) {
33-
// println("runType: $type, $workingDirectory")
34-
3563
phpCommandSettings.apply {
3664
addArgument("--suite")
3765
addArgument(type)
@@ -44,7 +72,6 @@ class TestoRunConfigurationHandler : PhpTestRunConfigurationHandler {
4472
directory: String,
4573
workingDirectory: String
4674
) {
47-
// println("runDirectory: $directory")
4875
if (directory.isEmpty()) return
4976

5077
phpCommandSettings.apply {
@@ -59,7 +86,6 @@ class TestoRunConfigurationHandler : PhpTestRunConfigurationHandler {
5986
file: String,
6087
workingDirectory: String
6188
) {
62-
// println("runFile: $file")
6389
if (file.isEmpty()) return
6490

6591
phpCommandSettings.apply {
@@ -75,25 +101,32 @@ class TestoRunConfigurationHandler : PhpTestRunConfigurationHandler {
75101
methodName: String,
76102
workingDirectory: String
77103
) {
78-
// println("runMethod: $file, $methodName")
79104
if (file.isEmpty()) return
80105

81-
val myMethodName = methodName.substringBefore('#')
82-
val dataProvider = methodName.substringAfter('#', "")
83-
84-
// println("method: $myMethodName, dataProvider: $dataProvider")
106+
val parsed = parseMethodName(methodName)
85107

86108
phpCommandSettings.apply {
87109
addArgument("--path")
88110
addRelativePathArgument(file, workingDirectory)
89-
if (myMethodName.isNotEmpty()) {
111+
if (parsed.method.isNotEmpty()) {
90112
addArgument("--filter")
91-
addArgument(myMethodName)
113+
addArgument(parsed.method)
92114
}
93-
if (dataProvider.isNotEmpty()) {
115+
if (parsed.dataProvider.isNotEmpty()) {
94116
addArgument("--data-provider")
95-
addArgument(dataProvider)
117+
addArgument(parsed.dataProvider)
96118
}
97119
}
98120
}
99-
}
121+
122+
data class ParsedMethodName(
123+
val method: String,
124+
val dataProvider: String,
125+
)
126+
127+
fun parseMethodName(methodName: String): ParsedMethodName {
128+
val method = methodName.substringBefore('#')
129+
val dataProvider = methodName.substringAfter('#', "")
130+
return ParsedMethodName(method, dataProvider)
131+
}
132+
}
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.xepozz.testo.tests.run
22

3+
import com.intellij.util.xmlb.annotations.Property
4+
import com.intellij.util.xmlb.annotations.Transient
35
import com.jetbrains.php.testFramework.run.PhpTestRunConfigurationSettings
46
import com.jetbrains.php.testFramework.run.PhpTestRunnerSettings
57

@@ -10,22 +12,24 @@ class TestoRunConfigurationSettings : PhpTestRunConfigurationSettings() {
1012

1113
override fun getRunnerSettings() = getTestoRunnerSettings()
1214

15+
@Transient
1316
override fun setRunnerSettings(runnerSettings: PhpTestRunnerSettings) {
1417
super.setRunnerSettings(TestoRunnerSettings.fromPhpTestRunnerSettings(runnerSettings))
1518
}
1619

20+
@Property(surroundWithTag = false)
1721
fun getTestoRunnerSettings(): TestoRunnerSettings {
1822
val settings = super.getRunnerSettings()
1923
if (settings is TestoRunnerSettings) {
2024
return settings
21-
} else {
22-
val copy = TestoRunnerSettings.fromPhpTestRunnerSettings(settings)
23-
this.setTestoRunnerSettings(copy)
24-
return copy
2525
}
26+
27+
val copy = TestoRunnerSettings.fromPhpTestRunnerSettings(settings)
28+
setTestoRunnerSettings(copy)
29+
return copy
2630
}
2731

2832
fun setTestoRunnerSettings(runnerSettings: TestoRunnerSettings) {
29-
this.setRunnerSettings(runnerSettings)
33+
setRunnerSettings(runnerSettings)
3034
}
31-
}
35+
}

src/main/kotlin/com/github/xepozz/testo/tests/run/TestoRunnerSettings.kt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
package com.github.xepozz.testo.tests.run
22

3+
import com.intellij.util.xmlb.annotations.Attribute
4+
import com.intellij.util.xmlb.annotations.Tag
35
import com.jetbrains.php.phpunit.coverage.PhpUnitCoverageEngine.CoverageEngine
46
import com.jetbrains.php.testFramework.run.PhpTestRunnerSettings
57

8+
@Tag("TestoRunnerSettings")
69
class TestoRunnerSettings(
710
var dataProviderIndex: Int = -1,
811
var dataSetIndex: Int = -1,
912
var coverageEngine: CoverageEngine = CoverageEngine.XDEBUG,
1013
var parallelTestingEnabled: Boolean = false,
14+
15+
@Attribute("command")
16+
var command: String = "run",
17+
18+
@Attribute("suite")
19+
var suite: String = "",
20+
21+
@Attribute("group")
22+
var group: String = "",
23+
24+
@Attribute("exclude_group")
25+
var excludeGroup: String = "",
26+
27+
@Attribute("repeat")
28+
var repeat: Int = 0,
29+
30+
@Attribute("parallel")
31+
var parallel: Int = 0,
1132
) : PhpTestRunnerSettings() {
1233
companion object Companion {
1334
@JvmStatic
@@ -23,7 +44,20 @@ class TestoRunnerSettings(
2344
runnerSettings.configurationFilePath = settings.configurationFilePath
2445
runnerSettings.testRunnerOptions = settings.testRunnerOptions
2546

47+
if (settings is TestoRunnerSettings) {
48+
runnerSettings.dataProviderIndex = settings.dataProviderIndex
49+
runnerSettings.dataSetIndex = settings.dataSetIndex
50+
runnerSettings.coverageEngine = settings.coverageEngine
51+
runnerSettings.parallelTestingEnabled = settings.parallelTestingEnabled
52+
runnerSettings.command = settings.command
53+
runnerSettings.suite = settings.suite
54+
runnerSettings.group = settings.group
55+
runnerSettings.excludeGroup = settings.excludeGroup
56+
runnerSettings.repeat = settings.repeat
57+
runnerSettings.parallel = settings.parallel
58+
}
59+
2660
return runnerSettings
2761
}
2862
}
29-
}
63+
}

0 commit comments

Comments
 (0)