Skip to content
This repository has been archived by the owner on Dec 7, 2019. It is now read-only.

Fix Logcat Parsing #133

Merged
merged 5 commits into from
Apr 19, 2018
Merged

Conversation

christopherperry
Copy link
Contributor

Fix for #129

Might need more testing.

}

// Implicitly expecting to see logs from `android.support.test.internal.runner.listener.LogRunListener`.
// Was not able to find more reliable solution to capture logcat per test.
val startedTest: Pair<String, String>? = newline.parseTestClassAndName("TestRunner: started: ")
val finishedTest: Pair<String, String>? = newline.parseTestClassAndName("TestRunner: finished: ")
val startedTest: Pair<String, String>? = newline.parseTestClassAndName("""(.*TestRunner.*: started: )(.*)\((.*)\)""".toRegex())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance to solve it without Regex?

My main concern is performance and readability :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered about perf, but personally find it very readable. It's easy to see the capture groups and destructuring makes it clear exactly what were pulling out of the log line.

I can give a non-regex implementation a shot too.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not hard, please try, would love to compare!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd compare it with non-regex solution, too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

fun String.parseTestClassAndName(): Pair<String, String>? {
    val tokens = split(':')
    if (tokens.size != 3) return null

    if (tokens[0].trimStart('I', '/').startsWith("TestRunner")) {
        if (tokens[1] == " started" || tokens[1] == " finished") {
            return tokens[2].substringAfter("(").removeSuffix(")") to tokens[2].substringBefore("(").trim()
        }
    }
    return null
}

I'm not sure about the performance of split(). Since it's a single character I'm assuming it's O(n) where n is the length of the string.

Copy link
Contributor Author

@christopherperry christopherperry Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the first check should be

if (!trimStart('I', '/').startsWith("TestRunner"))

so we can avoid O(n) on every log.

fun String.parseTestClassAndName(): Pair<String, String>? {
    if (!trimStart('I', '/').startsWith("TestRunner")) return null

    val tokens = split(':')
    if (tokens.size != 3) return null

    if (tokens[1] == " started" || tokens[1] == " finished") {
        return tokens[2].substringAfter("(").removeSuffix(")") to tokens[2].substringBefore("(").trim()
    }
    return null
}

else -> it.substringAfter("(").removeSuffix(")") to it.substringBefore("(")
}
fun String.parseTestClassAndName(regex: Regex): Pair<String, String>? {
return regex.find(this)?.destructured?.let { (_, testMethod, testClass) -> Pair(testMethod, testClass) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, typically to improve performance you create Pattern by Pattern.compile("…") once and use that

@artem-zinnatullin
Copy link
Collaborator

Btw, looks like you're committing with wrong git config, your commit doesn't have your avatar

You can check if commit email matches expected that is attached to github account:

git log

And if it's not, fix it like that:

git config user.email "[email protected]"
git commit -a --amend --reset-author

@artem-zinnatullin
Copy link
Collaborator

Ah, also please add test case(s) that show how old parsing failed and new one fixes that problem! Thanks :)

@christopherperry
Copy link
Contributor Author

@artem-zinnatullin Please have another look. I optimized and added some tests.

@@ -181,7 +181,21 @@ private fun pullTestFiles(adbDevice: AdbDevice, test: InstrumentationTest, outpu
)
}

private fun saveLogcat(adbDevice: AdbDevice, logsDir: File): Observable<Pair<String, String>> = Observable
class LogLineParser {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need class :)

As you can see, Composer is class-less other than data-classes, just drop a private top-level function :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't test it if it's private.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point


context("parse test class and name") {

it("parses old TestRunner start logs") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe give better name? "old" doesn't really explain what is old and what is new

Also, you can extract subject under test which is a log line that you parse into a separate variable for better test structure

val tokens = logLine.split(':')
if (tokens.size != 3) return null

if (tokens[1] == " started" || tokens[1] == " finished") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks vulnerable to spaces, maybe trim?

@@ -181,7 +181,21 @@ private fun pullTestFiles(adbDevice: AdbDevice, test: InstrumentationTest, outpu
)
}

private fun saveLogcat(adbDevice: AdbDevice, logsDir: File): Observable<Pair<String, String>> = Observable
class LogLineParser {
fun parseTestClassAndName(logLine: String): Pair<String, String>? {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly like this non-regexp version better, much more understandable!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a bug, I forgot the "newer" logs have a ton of stuff before "TestRunner". I'll submit an update later.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, so far it looks really good, keep it up 👍

@christopherperry
Copy link
Contributor Author

bump

Copy link
Collaborator

@artem-zinnatullin artem-zinnatullin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work!

}

it("extracts test class and method") {
assertThat(args).isEqualTo(Pair("com.example.SampleClass", "someTestMethod"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you can use infix function to create pair, it'll be little bit shorter

"com.example.SampleClass" to "someTestMethod"

import org.jetbrains.spek.api.dsl.context
import org.jetbrains.spek.api.dsl.it

class LogLineParserSpec : Spek({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great!

@artem-zinnatullin
Copy link
Collaborator

Just in case, I believe when GitHub will "Squash and Merge" it'll use first commit as base and might not associate it with your GitHub account, but I'm not sure

Possible solution: rebase with --reset-author and force push, but it's up2u :)

@christopherperry
Copy link
Contributor Author

Fixed. Thanks @artem-zinnatullin

@artem-zinnatullin
Copy link
Collaborator

@yunikkk can you PTAL and merge? :)

@evgengal
Copy link

Please, merge!)

@christopherperry
Copy link
Contributor Author

@evgengal I just built a jar and deployed that so I wouldn't have to wait. Put a fix for screenshots not working in there too, but it's kind of a special case.

@yunikkk yunikkk merged commit 5319795 into gojuno:master Apr 19, 2018
@yunikkk
Copy link
Contributor

yunikkk commented Apr 19, 2018

@christopherperry merged and released 0.3.3 with it, thanks!

@CristianGM
Copy link
Contributor

Well...now it doesn't work on emulators...
because of:
https://github.com/gojuno/composer/blob/master/composer/src/main/kotlin/com/gojuno/composer/TestRun.kt#L166

/storage/emulated/0 doesn't exist in emulators with API 22...

@Sloy
Copy link

Sloy commented Jun 14, 2018

I just created a new issue for what @CristianGM is commenting, in case you'd like to treat it as a different issue: #151

@yunikkk
Copy link
Contributor

yunikkk commented Jun 14, 2018

@CristianGM @Sloy thanks for reporting, adding fix.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants