-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
## **How To:** Debug / SSH into the GitHub actions workflow | ||
|
||
This document describes how to hook into the GitHub actions workflow if required using a remote SSH session. | ||
|
||
Add the following workflow step into any position of the `.github/workflows` file of your choice, e.g. [`voter.yml`](../../.github/workflows/voter.yml): | ||
|
||
```yaml | ||
- name: Setup tmate session | ||
uses: mxschmitt/action-tmate@v3 | ||
``` | ||
It causes a [tmate](https://tmate.io/) session to be spun up (attention it is a public session, anybody could hook into it, when they monitor our build logs, protection would be possible with options of the `action-tmate`). The action *will wait* until you connect to the session via SSH and create a `continue` file (e.g. by using `touch continue`). Wait for the step to execute, checking the build logs you should see an SSH info like: | ||
|
||
```bash | ||
SSH: ssh A7BF8dKH8jHw7KnzBRAUGB42c@sfo2.tmate.io | ||
``` | ||
|
||
This allows you to connect to connect to the container, before an action is executed, or afterwards, depending on the use case you are trying to debug. Connect to it via an SSH tool of your choice and hit `q` to close the welcome message. You are now on the machine, with step execution halted. in case you would like to continue the workflow create a file called `continue`: | ||
|
||
```bash | ||
touch continue | ||
``` | ||
|
||
**Important**: Don't forget to **remove** the action again, once you merge your change. The `tmate` action should not stay in the workflow, when being merged to the main branch. | ||
|
||
### Things you might be interested to do on the container | ||
|
||
```bash | ||
# grep all java processes running | ||
pgrep -fl java | ||
# or scan top in order to find out from a CPU point of view, which java process is most likely stuck | ||
top | ||
# use jstack to take a thread dump | ||
jstack 3303 > /tmp/threaddump.txt | ||
# use a service like https://transfer.sh/ to upload the dump | ||
curl --upload-file /tmp/threaddump.txt https://transfer.sh/threaddump.txt | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
## **How To:** Detect stuck tests | ||
|
||
With asynchronous execution of tests a missed `@Timeout` annotation and/or not signaling completion could easily lead to tests getting stuck. With the amount of unit tests, it can be hard to determine which tests get stuck when. A easy *non-intrusive* way of determining which tests are currently running / stuck, without having to enable full `showStandardStreams` in `build.gradle`, is to add a `beforeTest` and `afterTest` closure in `build.gradle`. To do so add the following variable above the `test { ... }` block: | ||
|
||
```groovy | ||
def runningTests = java.util.Collections.synchronizedSet(new java.util.HashSet()) | ||
``` | ||
|
||
Into the `test { ... }` block then add: | ||
|
||
```groovy | ||
beforeTest { descriptor -> | ||
runningTests.add(descriptor) | ||
logger.lifecycle("Start execution of test: " + descriptor) | ||
logger.lifecycle("Currently running: " + runningTests) | ||
} | ||
afterTest { descriptor, result -> | ||
runningTests.remove(descriptor) | ||
logger.lifecycle("Finished execution of test: " + descriptor) | ||
logger.lifecycle("Still running: " + runningTests) | ||
} | ||
``` | ||
|
||
To report which tests are currently still running. As soon as as a single stuck test has been identified, it often helps to log its console outputs: | ||
|
||
```groovy | ||
onOutput { descriptor, event -> | ||
if (descriptor.className == 'io.neonbee.test.AnyTestClassName') { | ||
logger.lifecycle("debug: " + event.message) | ||
} | ||
} | ||
``` | ||
|
||
Alternatively all standard output / error logs can be enabled using the following `testLogging` property: | ||
|
||
```groovy | ||
testLogging { showStandardStreams = true } | ||
``` | ||
|
||
*Attention:* Enabling all standard outputs will generate a huge log file! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
## **How To:** Disable Tests on GitHub only | ||
|
||
Use the following JUnit 5 annotation, to disable a test only for the voter on GitHub: | ||
|
||
```java | ||
@DisabledIfEnvironmentVariable(named = "GITHUB_ACTIONS", matches = "true", disabledReason = "provide any reason here") | ||
``` | ||
|
||
Before you disable the test on the GitHub workflow, it maybe makes sense trying to `@Isolate` the test first. This sometimes fixes any issues with the concurrent execution: | ||
|
||
```java | ||
@Isolated("provide any reason here") | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
## How To: Utilize `TestExecutionListener`s in JUnit 5 | ||
|
||
Have a look at some examples in our `src/test/java/io/neonbee/test/listeners` folder. | ||
|
||
Enable / disable the listeners by adding/removing them to/from the [`src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener`](../../src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener) file. JUnit 5 is loading all listeners, that are present in the file using the [Service Provider Interface (SPI)](https://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html) mechanism. Any line starting with a `#` in the service file will be ignored. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
## How To: Write an intercepting agent for debug purpose | ||
|
||
Sometimes it might be helpful to hook into literally **any** Java class of your choice for debug purpose. This is where a [*Java Agent*](https://www.baeldung.com/java-instrumentation) will come in handy. Utilizing Gradle it is super easy to write one, here is what you need to do: | ||
|
||
1. Create a subfolder `agent` in the root directory to put your Java Agent in | ||
2. Use the following basic `build.gradle` file, to prepare yourself doing byte code manipulation: | ||
|
||
```groovy | ||
plugins { | ||
id 'java' | ||
} | ||
repositories { | ||
mavenCentral() | ||
} | ||
dependencies { | ||
compileOnly group: 'org.slf4j', name: 'slf4j-api', version: '1.7.29' | ||
compileOnly group: 'net.bytebuddy', name: 'byte-buddy', version: '1.11.15' | ||
} | ||
tasks.withType(Jar) { | ||
manifest { | ||
attributes['Manifest-Version'] = '1.0' | ||
attributes['Premain-Class'] = 'io.neonbee.agent.AnyAgent' | ||
attributes['Can-Redefine-Classes'] = 'true' | ||
attributes['Can-Retransform-Classes'] = 'true' | ||
} | ||
} | ||
``` | ||
|
||
*Note*: How we have to define a `Premain-Class` in the manifest. We chose `io.neonbee.agent.AnyAgent`. | ||
3. Create the respective Java file in the `src/main/java/io/neonbee/agent/AnyAgent` directory of the `agent` subdirectory. For example: | ||
|
||
```java | ||
package io.neonbee.agent; | ||
|
||
import static net.bytebuddy.matcher.ElementMatchers.isConstructor; | ||
|
||
import java.lang.instrument.Instrumentation; | ||
import java.lang.reflect.Constructor; | ||
|
||
import net.bytebuddy.agent.builder.AgentBuilder; | ||
import net.bytebuddy.asm.Advice; | ||
import net.bytebuddy.asm.Advice.Argument; | ||
import net.bytebuddy.asm.Advice.Origin; | ||
import net.bytebuddy.asm.Advice.This; | ||
import net.bytebuddy.matcher.ElementMatchers; | ||
|
||
public class AnyAgent { | ||
public static void premain(String arguments, Instrumentation instrumentation) { | ||
new AgentBuilder.Default().type(ElementMatchers.named("io.vertx.core.impl.VertxImpl")) | ||
.transform((builder, typeDescription, classLoader, module) -> builder | ||
.visit(Advice.to(VertxImplConstructorInterceptor.class).on(isConstructor()))) | ||
.installOn(instrumentation); | ||
|
||
new AgentBuilder.Default().type(ElementMatchers.named("io.vertx.core.impl.VertxThread")) | ||
.transform((builder, typeDescription, classLoader, module) -> builder | ||
.visit(Advice.to(VertxThreadConstructorInterceptor.class).on(isConstructor()))) | ||
.installOn(instrumentation); | ||
} | ||
|
||
public static class VertxImplConstructorInterceptor { | ||
@Advice.OnMethodExit | ||
public static void intercept(@Origin Constructor<?> m, @This Object inst) throws Exception { | ||
new IllegalStateException("VERTX IMPL INIT!!").printStackTrace(); | ||
} | ||
} | ||
|
||
public static class VertxThreadConstructorInterceptor { | ||
@Advice.OnMethodExit | ||
public static void intercept(@Origin Constructor<?> m, @This Object inst, @Argument(1) String name) | ||
throws Exception { | ||
new IllegalStateException("VERTX THREAD INIT!! " + name + " (" + ((Thread) inst).getId() + ")") | ||
.printStackTrace(); | ||
} | ||
} | ||
} | ||
``` | ||
4. Next we have to add the following plugin into our root projects `build.gradle`s `plugin { ... }` block: | ||
|
||
```groovy | ||
id 'com.zoltu.application-agent' version '1.0.14' | ||
``` | ||
|
||
Add the following dependency to our `build.gradle`s `dependencies { ... }`: | ||
|
||
```groovy | ||
agent project(':agent') | ||
``` | ||
|
||
And add the following `include` statement into our `settings.gradle`: | ||
|
||
```groovy | ||
include 'agent' | ||
``` | ||
|
||
At the next start of the virtual machine, e.g. after running `./gradlew test` the instrumentation will be performed and the code associated to your `premain` method will get executed. |