Skip to content

Commit 0ed3ea6

Browse files
WIP
1 parent c03ee1a commit 0ed3ea6

File tree

6 files changed

+207
-29
lines changed

6 files changed

+207
-29
lines changed

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/basic/testsuitessources/fetch/TestSuitesSourceFetcher.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ fun ChildrenBuilder.testSuitesSourceFetcher(
106106
button {
107107
type = ButtonType.button
108108
className = ClassName("btn btn-primary mt-4")
109-
+"Fetch"
109+
+"Fetch" // XXX Should be disabled by default
110110
onClick = {
111111
triggerFetchTestSuiteSource()
112112
windowOpenness.closeWindow()

save-frontend/src/main/kotlin/com/saveourtool/save/frontend/components/views/TestSuiteValidationView.kt

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ val testSuiteValidationView: VFC = VFC {
104104
+"Validate test suites (application/x-ndjson)"
105105

106106
disabled = rawResponse !in arrayOf(null, READY, DONE)
107+
title = "Foo"
107108

108109
onClick = useNdjson(
109110
url = "$apiUrl/a/validate",
@@ -124,6 +125,7 @@ val testSuiteValidationView: VFC = VFC {
124125
+"Validate test suites (text/event-stream)"
125126

126127
disabled = rawResponse !in arrayOf(null, READY, DONE)
128+
title = "Bar"
127129

128130
onClick = useEventStream(
129131
url = "$apiUrl/a/validate",

save-frontend/webpack.config.d/dev-server.js

-5
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ config.devServer = Object.assign(
2121
proxyReq.setHeader("X-Authorization-Source", "basic");
2222
}
2323
},
24-
{
25-
context: ["/demo/api/**"],
26-
target: 'http://localhost:5421',
27-
logLevel: 'debug',
28-
},
2924
{
3025
context: ["/cpg/api/**"],
3126
target: 'http://localhost:5500',

save-preprocessor/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ dependencies {
2828
implementation(libs.ktoml.core)
2929
implementation(libs.kotlinx.serialization.json)
3030
implementation(libs.commons.compress)
31+
implementation(libs.arrow.kt.core)
3132
}

save-preprocessor/src/main/kotlin/com/saveourtool/save/preprocessor/service/TestDiscoveringService.kt

+153-23
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
package com.saveourtool.save.preprocessor.service
44

55
import com.saveourtool.save.core.config.TestConfig
6+
import com.saveourtool.save.core.config.TestConfigSections
67
import com.saveourtool.save.core.files.ConfigDetector
78
import com.saveourtool.save.core.plugin.GeneralConfig
9+
import com.saveourtool.save.core.plugin.PluginConfig
810
import com.saveourtool.save.core.plugin.PluginException
911
import com.saveourtool.save.core.utils.buildActivePlugins
1012
import com.saveourtool.save.core.utils.processInPlace
1113
import com.saveourtool.save.entities.TestSuite
14+
import com.saveourtool.save.plugin.warn.WarnPluginConfig
1215
import com.saveourtool.save.plugins.fix.FixPlugin
16+
import com.saveourtool.save.plugins.fixandwarn.FixAndWarnPluginConfig
1317
import com.saveourtool.save.preprocessor.utils.toHash
1418
import com.saveourtool.save.test.TestDto
1519
import com.saveourtool.save.test.TestsSourceSnapshotDto
@@ -18,10 +22,14 @@ import com.saveourtool.save.testsuite.TestSuiteDto
1822
import com.saveourtool.save.utils.EmptyResponse
1923
import com.saveourtool.save.utils.blockingToMono
2024
import com.saveourtool.save.utils.debug
25+
import com.saveourtool.save.utils.error
2126
import com.saveourtool.save.utils.info
2227
import com.saveourtool.save.utils.requireIsAbsolute
2328
import com.saveourtool.save.utils.thenJust
24-
29+
import arrow.core.Either
30+
import arrow.core.getOrElse
31+
import arrow.core.left
32+
import arrow.core.right
2533
import okio.FileSystem
2634
import okio.Path
2735
import okio.Path.Companion.toOkioPath
@@ -35,9 +43,14 @@ import reactor.kotlin.core.publisher.toFlux
3543
import reactor.kotlin.core.publisher.toMono
3644
import reactor.kotlin.core.util.function.component1
3745
import reactor.kotlin.core.util.function.component2
46+
import java.util.regex.PatternSyntaxException
3847
import kotlin.io.path.absolute
3948
import kotlin.io.path.div
40-
49+
import kotlin.io.path.isDirectory
50+
import kotlin.io.path.isRegularFile
51+
import kotlin.io.path.listDirectoryEntries
52+
import kotlin.io.path.name
53+
import kotlin.io.path.relativeToOrNull
4154
import java.nio.file.Path as NioPath
4255

4356
/**
@@ -60,16 +73,22 @@ class TestDiscoveringService(
6073
sourceSnapshot: TestsSourceSnapshotDto,
6174
): Mono<List<TestSuite>> {
6275
log.info { "Starting to save new test suites for root test config in $repositoryPath" }
63-
return blockingToMono {
76+
val rootTestConfigAsync = blockingToMono {
6477
getRootTestConfig((repositoryPath / testRootPath).absolute().normalize())
6578
}
79+
80+
return rootTestConfigAsync
6681
.zipWhen { rootTestConfig ->
6782
{
6883
log.info { "Starting to discover test suites for root test config ${rootTestConfig.location}" }
69-
discoverAllTestSuites(
84+
val testSuites: List<TestSuiteDto> = discoverAllTestSuites(
7085
rootTestConfig,
7186
sourceSnapshot,
7287
)
88+
testSuites.forEach { testSuite ->
89+
log.info { "XXX " }
90+
}
91+
testSuites
7392
}.toMono()
7493
}
7594
.flatMap { (rootTestConfig, testSuites) ->
@@ -106,28 +125,138 @@ class TestDiscoveringService(
106125
* @throws IllegalArgumentException when provided path doesn't point to a valid config file
107126
*/
108127
@NonBlocking
109-
@Suppress("UnsafeCallOnNullableType")
128+
@Suppress(
129+
"UnsafeCallOnNullableType",
130+
"LongMethod",
131+
"MagicNumber",
132+
"RedundantHigherOrderMapUsage",
133+
"VARIABLE_NAME_INCORRECT",
134+
"TOO_LONG_FUNCTION",
135+
"WRONG_NEWLINES",
136+
)
110137
fun getAllTestSuites(
111138
rootTestConfig: TestConfig,
112139
sourceSnapshot: TestsSourceSnapshotDto,
113-
) = rootTestConfig
114-
.getAllTestConfigs()
115-
.asSequence()
116-
.mapNotNull { it.getGeneralConfigOrNull() }
117-
.filterNot { it.suiteName == null }
118-
.filterNot { it.description == null }
119-
.map { config ->
120-
// we operate here with suite names from only those TestConfigs, that have General section with suiteName key
121-
TestSuiteDto(
122-
config.suiteName!!,
123-
config.description,
124-
sourceSnapshot,
125-
config.language,
126-
config.tags
127-
)
140+
): List<TestSuiteDto> {
141+
log.info { "XXX getAllTestSuites()" }
142+
val t0 = System.nanoTime()
143+
val allTestConfigs = rootTestConfig
144+
.getAllTestConfigs()
145+
val t1 = System.nanoTime()
146+
@Suppress("FLOAT_IN_ACCURATE_CALCULATIONS")
147+
log.info { "XXX getAllTestConfigs() took ${(t1 - t0) / 1000L / 1e3} ms" }
148+
149+
return allTestConfigs
150+
.asSequence()
151+
.map { testConfig: TestConfig -> // XXX Replace with onEach or forEach?
152+
log.info { "XXX Test config: $testConfig" }
153+
val pluginConfigs = testConfig.pluginConfigs
154+
.asSequence()
155+
.filterNot { config ->
156+
config is GeneralConfig
157+
}
158+
159+
@Suppress("GENERIC_VARIABLE_WRONG_DECLARATION")
160+
val errors = mutableListOf<String>()
161+
162+
@Suppress("TYPE_ALIAS")
163+
val resources: MutableMap<TestConfigSections, MutableList<NioPath>> = pluginConfigs.fold(mutableMapOf()) { acc, config ->
164+
acc.apply {
165+
compute(config.type) { _, valueOrNull: MutableList<NioPath>? ->
166+
val resourceNames = config.getResourceNames().getOrElse { error ->
167+
errors += error
168+
emptySequence()
169+
}
170+
171+
(valueOrNull ?: mutableListOf()).apply {
172+
addAll(resourceNames)
173+
}
174+
}
175+
}
176+
}
177+
178+
resources.forEach { type, resourceNames ->
179+
log.info { "\t$type" }
180+
resourceNames.forEach {
181+
log.info { "\t\t$it" }
182+
}
183+
}
184+
errors.forEach { error ->
185+
log.info { "\t$error" }
186+
}
187+
188+
val warnConfigs = pluginConfigs.asSequence().mapNotNull { config ->
189+
when (config) {
190+
is WarnPluginConfig -> config
191+
is FixAndWarnPluginConfig -> config.warn
192+
else -> null
193+
}
194+
}.toList()
195+
log.info { "XXX Warn configs: ${warnConfigs.size}" }
196+
warnConfigs.forEachIndexed { index, config ->
197+
log.info { "\t$index: wildCardInDirectoryMode = ${config.wildCardInDirectoryMode}" } // XXX Should be null
198+
}
199+
200+
testConfig
201+
}
202+
.mapNotNull { it.getGeneralConfigOrNull() }
203+
.filterNot { it.suiteName == null }
204+
.filterNot { it.description == null }
205+
.map { generalConfig: GeneralConfig ->
206+
log.info { "XXX General config: $generalConfig" }
207+
generalConfig
208+
}
209+
.map { config ->
210+
// we operate here with suite names from only those TestConfigs, that have General section with suiteName key
211+
TestSuiteDto(
212+
config.suiteName!!,
213+
config.description,
214+
sourceSnapshot,
215+
config.language,
216+
config.tags
217+
)
218+
}
219+
.distinct()
220+
.toList()
221+
}
222+
223+
@Suppress("TYPE_ALIAS", "WRONG_NEWLINES")
224+
private fun PluginConfig.getResourceNames(): Either<String, Sequence<NioPath>> {
225+
val resourceNamePattern = try {
226+
Regex(resourceNamePatternStr)
227+
} catch (_: PatternSyntaxException) {
228+
return "Resource name pattern is not a valid regular expression: \"$resourceNamePatternStr\"".left()
128229
}
129-
.distinct()
130-
.toList()
230+
231+
val configLocation = configLocation.toNioPath()
232+
if (!configLocation.isRegularFile()) {
233+
return "Config file doesn't exist: \"$configLocation\"".left()
234+
}
235+
236+
val testDirectory = configLocation.parent
237+
?: return "The parent directory of the config file is null: \"$configLocation\"".left()
238+
if (!testDirectory.isDirectory()) {
239+
return "Test directory doesn't exist: \"$testDirectory\"".left()
240+
}
241+
242+
return testDirectory.listDirectoryEntries().asSequence()
243+
.filter(NioPath::isRegularFile)
244+
.filterNot { file ->
245+
file.name.equals("save.toml", ignoreCase = true)
246+
}
247+
.filterNot { file ->
248+
file.name.equals("save.properties", ignoreCase = true)
249+
}
250+
.map { file ->
251+
file.relativeToOrNull(testDirectory)
252+
}
253+
.filterNotNull()
254+
.filterNot(NioPath::isAbsolute)
255+
.filter { relativeFile ->
256+
relativeFile.name.matches(resourceNamePattern)
257+
}
258+
.right()
259+
}
131260

132261
/**
133262
* Discover all test suites in the project
@@ -142,7 +271,8 @@ class TestDiscoveringService(
142271
fun discoverAllTestSuites(
143272
rootTestConfig: TestConfig,
144273
sourceSnapshot: TestsSourceSnapshotDto,
145-
) = getAllTestSuites(rootTestConfig, sourceSnapshot)
274+
): List<TestSuiteDto> =
275+
getAllTestSuites(rootTestConfig, sourceSnapshot)
146276

147277
private fun Path.getRelativePath(rootTestConfig: TestConfig) = this.toFile()
148278
.relativeTo(rootTestConfig.directory.toFile())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.saveourtool.save.preprocessor.service
2+
3+
import com.saveourtool.save.test.TestSuiteValidationError
4+
import com.saveourtool.save.test.TestSuiteValidationProgress
5+
import com.saveourtool.save.utils.getLogger
6+
import org.springframework.jmx.export.annotation.ManagedOperation
7+
import org.springframework.jmx.export.annotation.ManagedResource
8+
import org.springframework.stereotype.Service
9+
10+
@Suppress(
11+
"MISSING_KDOC_TOP_LEVEL",
12+
"MISSING_KDOC_CLASS_ELEMENTS",
13+
"MISSING_KDOC_ON_FUNCTION"
14+
)
15+
@Service
16+
@ManagedResource
17+
class TestSuiteValidatorClient(private val service: TestSuiteValidationService) {
18+
@ManagedOperation
19+
fun validate() {
20+
service.validateAll(emptyList())
21+
.subscribe()
22+
}
23+
24+
@ManagedOperation
25+
fun validateAndRequestSingle() {
26+
service.validateAll(emptyList())
27+
.doOnNext { status ->
28+
when (status) {
29+
is TestSuiteValidationProgress -> logger.info("First status event received: ${status.percentage}%")
30+
is TestSuiteValidationError -> logger.info("Error: ${status.message}")
31+
}
32+
}
33+
.sequential()
34+
.next()
35+
.block()
36+
}
37+
38+
@ManagedOperation
39+
fun validateAndCancel() {
40+
service.validateAll(emptyList())
41+
.subscribe()
42+
.dispose()
43+
logger.info("Subscription for status update events cancelled.")
44+
}
45+
46+
private companion object {
47+
@Suppress("GENERIC_VARIABLE_WRONG_DECLARATION")
48+
private val logger = getLogger<TestSuiteValidatorClient>()
49+
}
50+
}

0 commit comments

Comments
 (0)