Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS]Added Support for Cucumberish Tags / Function Annotations #702

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ sealed class VendorConfiguration {
@JsonProperty("compactOutput") val compactOutput: Boolean = false,
@JsonProperty("keepAliveIntervalMillis") val keepAliveIntervalMillis: Long = 0L,
@JsonProperty("devices") val devicesFile: File? = null,
) : VendorConfiguration() {
@JsonProperty("xcTestRunnerTag") val xcTestRunnerTag: String? = null,
) : VendorConfiguration() {
/**
* Exception should not happen since it will be first thrown in deserializer
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ class ConfigurationFactoryTest {
keepAliveIntervalMillis = 300000L,
devicesFile = file.parentFile.resolve("Testdevices").canonicalFile,
sourceRoot = file.parentFile.resolve(".").canonicalFile,
xcTestRunnerTag = "demoTag"
)
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/kotlin/com/malinskiy/marathon/Marathon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Marathon(
val shard = prepareTestShard(tests, analytics)

log.info("Scheduling ${tests.size} tests")
log.debug(tests.joinToString(", ") { it.toTestName() })
log.info(tests.joinToString(", ") { it.toTestName() })
val currentCoroutineContext = coroutineContext
val scheduler = Scheduler(
deviceProvider,
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/kotlin/com/malinskiy/marathon/test/Test.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,13 @@ fun Test.toClassName(separator: Char = '.'): String {
fun Test.toSimpleSafeTestName(): String = "$clazz.$method"

fun Test.toSafeTestName() = toTestName(methodSeparator = '.')

fun Test.getCucumberishTags(): List<String>{
var tags: List<String> = emptyList()

if (metaProperties.isNotEmpty()) {
val mappedValues = metaProperties.filter { it.name == "cucumberishTags" }.first().values
tags = mappedValues.get("tags") as? List<String> ?: emptyList()
}
return tags
}
8 changes: 8 additions & 0 deletions docs/_posts/2018-11-19-ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,11 @@ this here
```yaml
devicesFile: "/opt/Marathondevices"
```

## Annotating Test Function Using Cucumberish @Tags
You can annotate your test functions with cucumberish tags for ex: `@SmokeTest @RegressionTest` etc.
If your Marathon file contains the parameter `xcTestRunnerTag`, then only the selected test functions will be executed by marathon.

```yaml
xcTestRunnerTag: "SmokeTest"
```
1 change: 1 addition & 0 deletions sample/ios-app/Marathonfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ vendorConfiguration:
knownHostsPath: ${HOME}/.ssh/known_hosts
remoteUsername: ${USER}
remotePrivateKey: ${HOME}/.ssh/marathon
xcTestRunnerTag: "SmokeTest"
13 changes: 13 additions & 0 deletions sample/ios-app/sample-appUITests/StoryboardTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ class StoryboardTests: XCTestCase {
let label = app.staticTexts.firstMatch
XCTAssertEqual(label.label, "Label")
}

// @SmokeTest @RegressionTest
func testButtonBasedOnAnnotationOrTag() {
let button = app.buttons.firstMatch
XCTAssertTrue(button.waitForExistence())
XCTAssertTrue(button.isHittable)

button.tap()

let label = app.staticTexts.firstMatch
XCTAssertTrue(label.waitForExistence())
}

}

private let standardTimeout: TimeInterval = 30.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import com.malinskiy.marathon.config.vendor.VendorConfiguration
import com.malinskiy.marathon.execution.TestParser
import com.malinskiy.marathon.ios.xctestrun.Xctestrun
import com.malinskiy.marathon.log.MarathonLogging
import com.malinskiy.marathon.test.MetaProperty
import com.malinskiy.marathon.test.Test
import com.malinskiy.marathon.test.getCucumberishTags
import java.io.File

class IOSTestParser(private val vendorConfiguration: VendorConfiguration.IOSConfiguration) : TestParser {
private val swiftTestClassRegex = """class ([^:\s]+)\s*:\s*XCTestCase""".toRegex()
private val swiftTestMethodRegex = """^.*func\s+(test[^(\s]*)\s*\(.*$""".toRegex()
private val swiftTagRegex = """@([^:\s]+)\w*""".toRegex()

private val logger = MarathonLogging.logger(IOSTestParser::class.java.simpleName)
private val CUCUMBERISH_TAGS = "cucumberishTags"

/**
* Looks up test methods running a text search in swift files. Considers classes that explicitly inherit
Expand All @@ -35,7 +39,7 @@ class IOSTestParser(private val vendorConfiguration: VendorConfiguration.IOSConf
val implementedTests = mutableListOf<Test>()
for (file in swiftFilesWithTests) {
var testClassName: String? = null
for (line in file.readLines()) {
for ((index, line) in file.readLines().withIndex()) {
val className = line.firstMatchOrNull(swiftTestClassRegex)
val methodName = line.firstMatchOrNull(swiftTestMethodRegex)

Expand All @@ -44,18 +48,47 @@ class IOSTestParser(private val vendorConfiguration: VendorConfiguration.IOSConf
}

if (testClassName != null && methodName != null) {
implementedTests.add(Test(targetName, testClassName, methodName, emptyList()))
// Find & Update Tags here
val tagLine = file.readLines()[index - 1]
val tags = swiftTagRegex.findAll(tagLine).map { it.groupValues[1] }.toList()

if (tags.isEmpty()) {
implementedTests.add(Test(targetName, testClassName, methodName, emptyList()))
}
else {
val values = mapOf("tags" to tags)
val metaProperty = MetaProperty(name = CUCUMBERISH_TAGS, values = values)
implementedTests.add(Test(targetName, testClassName, methodName, arrayListOf(metaProperty)))
}
}
}
}

val filteredTests = implementedTests.filter { !xctestrun.isSkipped(it) }
var filteredTests = implementedTests.filter { !xctestrun.isSkipped(it) }

filteredTests = filterByXCTestRunnerTag(filteredTests)

logger.trace { filteredTests.map { "${it.clazz}.${it.method}" }.joinToString() }
logger.info { "Found ${filteredTests.size} tests in ${swiftFilesWithTests.count()} files" }

return filteredTests
}

private fun filterByXCTestRunnerTag(tests: List<Test>): List<Test> {
if (vendorConfiguration.xcTestRunnerTag.isNullOrEmpty()) {
return tests
}

var filteredTests = tests
val testRunnerTag = vendorConfiguration.xcTestRunnerTag ?: ""

if (testRunnerTag.isNotEmpty()) {
filteredTests = filteredTests.filter {
it.getCucumberishTags().contains(testRunnerTag)
}
}
return filteredTests
}
}

private fun Sequence<File>.filter(contentsRegex: Regex): Sequence<File> {
Expand Down