Use API instead of XML for result parsing for android#666
Conversation
| // it is making api calls under the hood | ||
| testExecution.createTestExecutionData() | ||
| } | ||
| }.map { deferred -> |
There was a problem hiding this comment.
I think we can use .awaitAll() instead, just for simplification
| args: IArgs | ||
| ): JUnitTestResult = GcTestMatrix.refresh( | ||
| // Android uses server side sharding, it results only one matrix, so we can simply get first. | ||
| matrices.map.values.first().matrixId, |
There was a problem hiding this comment.
iOS doesn't use server side sharding right now. Is it possible to use the API for both Android & iOS?
Edit: I see that processXmlFromApi is only used for Android right now. That makes sense.
| import java.io.File | ||
| import kotlin.system.exitProcess | ||
|
|
||
| object TmpV2 { |
There was a problem hiding this comment.
we should probably delete TmpV2 and turn this into a proper JUnit test. What do you think?
There was a problem hiding this comment.
Also probably delete the original tmp
There was a problem hiding this comment.
Of Course I will do that. This is only draft of implementation that I tested manually and looks correct. I now need to update unit tests, cleanup a little, and It will be ready for review.
| package ftl.reports.api | ||
|
|
||
| import com.google.api.services.toolresults.model.Duration | ||
| import java.util.* |
There was a problem hiding this comment.
I think we have a lint rule about no wildcard imports.
| println(" - OK") | ||
|
|
||
| print("writing api results to file") | ||
| File(JUNIT_REPORT_FILE).writeText(jUnitTestResult.xmlToString()) |
There was a problem hiding this comment.
To test this, run an Android app with multiple tests using server side sharding that has flaky tests. Use num-flaky-test-attempts set to a non-zero value such as 2. Then compare the JUnit XML file that FTL is creating vs the one we create via the API.
There was a problem hiding this comment.
I was testing exactly that case. I know there is a difference between old and new JUnitReport.xml.
Old:
<?xml version='1.0' encoding='UTF-8' ?>
<testsuites>
<testsuite name="NexusLowRes-28-en-portrait#" tests="3" failures="0" errors="0" skipped="0" time="3.103" timestamp="2020-03-10T17:12:24" hostname="localhost">
<testcase name="test0" classname="com.example.test_app.InstrumentedTest" time="1.161">
<webLink></webLink>
</testcase>
<testcase name="test1" classname="com.example.test_app.InstrumentedTest" time="1.034">
<webLink></webLink>
</testcase>
<testcase name="test2" classname="com.example.test_app.InstrumentedTest" time="0.908">
<webLink></webLink>
</testcase>
</testsuite>
</testsuites>
New:
<?xml version='1.0' encoding='UTF-8' ?>
<testsuites>
<testsuite name="NexusLowRes-28-en-portrait" tests="1" failures="0" errors="0" skipped="0" time="1.161" timestamp="2020-03-10T17:07:54.422Z">
<testcase name="test0" classname="com.example.test_app.InstrumentedTest" time="1.161"/>
</testsuite>
<testsuite name="NexusLowRes-28-en-portrait" tests="2" failures="0" errors="0" skipped="0" time="1.942" timestamp="2020-03-10T17:07:54.422Z">
<testcase name="test1" classname="com.example.test_app.InstrumentedTest" time="1.034"/>
<testcase name="test2" classname="com.example.test_app.InstrumentedTest" time="0.908"/>
</testsuite>
</testsuites>
-
In new JUnitReport suites reflects shards. I can fix it easily, but that was first what I achieved. I guess that maybe is better to expose some info about shards in JUnitReport. If you don't like that I will fix it.
-
The
timestampis different yes. Old timestamp is simply timestamp of first of downloaded reports. New one is not correct I will fix it. -
I need to also fix weblinks.
|
When using Android Test Orchestrator, we'll want to verify the API test times match the times returned in the FTL XML file. |
7d905dc to
15d9f39
Compare
Codecov Report
@@ Coverage Diff @@
## master #666 +/- ##
============================================
- Coverage 77.10% 76.10% -1.00%
- Complexity 598 615 +17
============================================
Files 99 109 +10
Lines 2367 2528 +161
Branches 337 359 +22
============================================
+ Hits 1825 1924 +99
- Misses 335 381 +46
- Partials 207 223 +16 |
80ecae9 to
25e5867
Compare
There was a problem hiding this comment.
Great job! That is one big PR with lot of work. I am really impressed.
The only thing I can complain is multiple usage of runBlocking. Personally I don't like when this is used as kind of bridge between synchronous and asynchronous code (when the first one is invoked inside a coroutine). But TBH I don't know if we have better solution right now.
Again, great job!
| return listOf(map["Model"], map["Version"], map["Locale"], map["Orientation"]).joinToString("-") | ||
| } | ||
|
|
||
| private fun List<JUnitTestCase>.sumTime() = this |
There was a problem hiding this comment.
I think this is redundant here but I guess it was left intentionally for sake of readability?
There was a problem hiding this comment.
That's right, intentionally for readability. I usually use this for extension functions with expression body. Thats allow clear chain.
| .getTestExecutions() | ||
| .createJUnitTestResult() | ||
|
|
||
| internal fun refreshTestMatrices( |
There was a problem hiding this comment.
I think we can make it private
| }.awaitAll() | ||
| } | ||
|
|
||
| internal fun List<TestMatrix>.getTestExecutions(): List<TestExecution> = this |
There was a problem hiding this comment.
I think we can make it private
| skipped = if (testCase.status == "skipped") null else "absent" | ||
| ).apply { | ||
| if (errors != null || failures != null) { | ||
| webLink = getWebLink(toolResultsStep, testCase.testCaseId) |
|
|
||
| private fun TestExecution.createTestExecutionData(): TestExecutionData { | ||
| val response: ListTestCasesResponse = GcToolResults.listTestCases(toolResultsStep) | ||
| val step: Step = GcToolResults.getStepResult(toolResultsStep) |
There was a problem hiding this comment.
Both GcToolResults.listTestCases and GcToolResults.getStepResult are making api calls and seems that they don't depend on each other. Do you think we could make async call for each?
There was a problem hiding this comment.
Right, I will fix that.
|
|
||
| fun Long.formatUtcDate() = utcDateFormat.format(this)!! | ||
|
|
||
| var TestCase.flaky: Boolean by mutableMapProperty { false } |
| @@ -0,0 +1,34 @@ | |||
| package ftl.reports.api | |||
There was a problem hiding this comment.
We already have Utils.kt file, can we rename this one to sth like ApiUtils or somehow different? Just for sake of clarity.
There was a problem hiding this comment.
Looks like you don't care about packages :). I guess adding part of package as prefix for file is redundant.
There was a problem hiding this comment.
Yeah, true. Sometimes I am kind of bitchy :D ¯_(ツ)_/¯
There was a problem hiding this comment.
Easy. You are doing your job. It is always better to express your opinion than to do nothing. There are no ultimate ways, everything we do is based on consensus.
| internal val xmlPrettyWriter = xmlMapper.writerWithDefaultPrettyPrinter() | ||
|
|
||
| private fun xmlBytes(path: Path): ByteArray { | ||
| if (!path.toFile().exists()) RuntimeException("$path doesn't exist!") |
There was a problem hiding this comment.
I am totally aware it is not part of your PR (so feel free to skip this comment). I think there is throw missing
There was a problem hiding this comment.
Good checkup. I can fix it in this PR, no problem.
|
|
||
| @JacksonXmlProperty(isAttribute = true) | ||
| val hostname: String, // String. | ||
| val hostname: String? = null, // String. |
There was a problem hiding this comment.
Just for my understanding, are we ok with possible null here? Previous implementation did not predicted such value. Thanks!
There was a problem hiding this comment.
I have asked @bootstraponline about that, he said #545 (comment). So I decide to change it to nullable here, because I didn't find hostname in API
There was a problem hiding this comment.
Got it, thanks for explanation
| Paths.get(args.localResultDir, FtlConstants.configFileName(args)) | ||
| } else { | ||
| val configFilePath = if (args.useLocalResultDir()) | ||
| Paths.get(args.localResultDir, FtlConstants.configFileName(args)) else |
There was a problem hiding this comment.
TBH I don't like it when I need to read whole line to find out there is else at the end. But it is matter of personal preferences :)
There was a problem hiding this comment.
TBH I don't like if else and prefer using when but our detect config forces to use if else in cases like this. Anyway I like that kind of formatting cause of clear indentation same for two similar parts.
3d5e8bf
* Fix coroutines errors after rebase
e7bd0b0 to
169c7b5
Compare
Fixes #618, Fixes #545
Checklist