-
Notifications
You must be signed in to change notification settings - Fork 749
Provide JUnit5 compatible alternative for RoundTripAbstractTest and RestartableJenkinsRule #2623
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
Merged
timja
merged 3 commits into
jenkinsci:master
from
strangelookingnerd:alternative_for_restartable_jenkins_rule
Jan 28, 2025
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
12 changes: 7 additions & 5 deletions
12
integrations/src/test/java/io/jenkins/plugins/casc/RoundTripMailerTest.java
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
211 changes: 211 additions & 0 deletions
211
...rness/src/main/java/io/jenkins/plugins/casc/misc/junit/jupiter/AbstractRoundTripTest.java
This file contains hidden or 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,211 @@ | ||
| package io.jenkins.plugins.casc.misc.junit.jupiter; | ||
|
|
||
| import static org.hamcrest.MatcherAssert.assertThat; | ||
| import static org.hamcrest.Matchers.containsString; | ||
| import static org.htmlunit.HttpMethod.POST; | ||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import io.jenkins.plugins.casc.ConfigurationAsCode; | ||
| import io.jenkins.plugins.casc.ConfiguratorException; | ||
| import java.io.File; | ||
| import java.io.FileNotFoundException; | ||
| import java.io.IOException; | ||
| import java.io.PrintWriter; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.nio.file.Path; | ||
| import java.util.Collections; | ||
| import java.util.logging.Level; | ||
| import org.apache.commons.io.IOUtils; | ||
| import org.htmlunit.WebRequest; | ||
| import org.htmlunit.WebResponse; | ||
| import org.htmlunit.util.NameValuePair; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.api.io.TempDir; | ||
| import org.jvnet.hudson.test.JenkinsRule; | ||
| import org.jvnet.hudson.test.LogRecorder; | ||
| import org.jvnet.hudson.test.junit.jupiter.WithJenkins; | ||
|
|
||
| /** | ||
| * Base test to check a complete test of each plugin configuration. | ||
| * <p> | ||
| * What it does: | ||
| * <ol> | ||
| * <li>Configure the instance with the {@link #configResource()} implemented.</li> | ||
| * <li>Check it was configured correctly.</li> | ||
| * <li>Check the configuration is valid via Web UI.</li> | ||
| * <li>Apply the configuration via Web UI.</li> | ||
| * <li>Write the configuration to $JENKINS_HOME/jenkins.yaml.</li> | ||
| * <li>Restart Jenkins.</li> | ||
| * <li>Check the {@link #stringInLogExpected()} is set during the restart.</li> | ||
| * <li>Check it is still configured correctly after the restart</li> | ||
| * </ol> | ||
| * <p> | ||
| * All the plugin author needs to do is override the methods providing: | ||
| * <ol> | ||
| * <li>The resource with the yaml configuration of the plugin in case they use their own name for the file.</li> | ||
| * <li>A way to validate the configuration is established.</li> | ||
| * <li>A string that should be present in the logs (casc logger) that guarantees the config is loaded. Usually a weird text configured.</li> | ||
| * </ol> | ||
| * <p> | ||
| * This is the JUnit5 equivalent of {@link io.jenkins.plugins.casc.misc.RoundTripAbstractTest} | ||
strangelookingnerd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * | ||
| * @see io.jenkins.plugins.casc.misc.RoundTripAbstractTest | ||
strangelookingnerd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| */ | ||
| @WithJenkins | ||
| public abstract class AbstractRoundTripTest { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used a slightly different name besides a different package to reduce the risk of mix-ups. Not sure if this is the best way, so feedback is welcome. |
||
|
|
||
| @TempDir | ||
| public Path tempFolder; | ||
|
|
||
| /** | ||
| * A method to assert if the configuration was correctly loaded. The Jenkins rule and the | ||
| * content of the config supposedly loaded are passed. | ||
| * | ||
| * @param j a JenkinsRule instance. | ||
| * @param configContent expected configuration. | ||
| */ | ||
| protected abstract void assertConfiguredAsExpected(JenkinsRule j, String configContent); | ||
|
|
||
| /** | ||
| * Return the resource path (yaml file) to be loaded. i.e: If the resource is in the same | ||
| * package of the implementor class, then: my-config.yaml | ||
| * | ||
| * @return the resource name and path. | ||
| */ | ||
| protected String configResource() { | ||
| return "configuration-as-code.yaml"; | ||
| } | ||
|
|
||
| /** | ||
| * Return the string that should be in the logs of the JCasC logger to verify it's configured | ||
| * after a restart. This string should be unique to avoid interpreting that it was configured | ||
| * successfully, but it wasn't. | ||
| * | ||
| * @return the unique string to be in the logs to certify the configuration was done | ||
| * successfully. | ||
| */ | ||
| protected abstract String stringInLogExpected(); | ||
|
|
||
| /** | ||
| * 1. Configure the instance with the {@link #configResource()} implemented. 2. Check it was | ||
| * configured correctly. 3. Check the configuration is valid via Web UI. 4. Apply the | ||
| * configuration via Web UI. 5. Write the configuration to $JENKINS_HOME/jenkins.yaml. 6. | ||
| * Restart Jenkins. 7. Check the {@link #stringInLogExpected()} is set during the restart. | ||
| * | ||
| * @throws IOException If an exception is thrown managing resources or files. | ||
| */ | ||
| @Test | ||
| public void roundTripTest(JenkinsRule r) throws Throwable { | ||
| String resourcePath = configResource(); | ||
| String resourceContent = getResourceContent(resourcePath); | ||
|
|
||
| assertNotNull(resourcePath); | ||
| assertNotNull(resourceContent); | ||
|
|
||
| // Configure and validate | ||
| configureWithResource(resourcePath); | ||
| assertConfiguredAsExpected(r, resourceContent); | ||
|
|
||
| // Check config is valid via Web UI | ||
| String jenkinsConf = getResourceContent(resourcePath); | ||
| assertConfigViaWebUI(r, jenkinsConf); | ||
|
|
||
| // Apply configuration via Web UI | ||
| applyConfigViaWebUI(r, jenkinsConf); | ||
| assertConfiguredAsExpected(r, resourceContent); | ||
|
|
||
| // Configure Jenkins default JCasC file with the config file. It's already established, we check if applied | ||
| // looking at the logs. | ||
| putConfigInHome(r, jenkinsConf); | ||
|
|
||
| // Start recording the logs just before restarting, to avoid capture the previous startup. We're look there | ||
| // if the "magic token" is there | ||
| try (LogRecorder recorder = | ||
| new LogRecorder().record("io.jenkins.plugins.casc", Level.FINER).capture(2048)) { | ||
| // Restart the testing instance | ||
| r.restart(); | ||
|
|
||
| // Verify the log shows it's configured | ||
| assertLogAsExpected(recorder, stringInLogExpected()); | ||
| } | ||
|
|
||
| // Verify the configuration set at home/jenkins.yaml is loaded | ||
| assertConfiguredAsExpected(r, resourceContent); | ||
| } | ||
|
|
||
| private void configureWithResource(String config) throws ConfiguratorException { | ||
| ConfigurationAsCode.get().configure(this.getClass().getResource(config).toExternalForm()); | ||
| } | ||
|
|
||
| private String getResourceContent(String resourcePath) throws IOException { | ||
| return IOUtils.toString(getClass().getResourceAsStream(resourcePath), StandardCharsets.UTF_8); | ||
| } | ||
|
|
||
| private void writeToFile(String text, String path) throws FileNotFoundException { | ||
| File file = new File(path); | ||
| try (PrintWriter out = new PrintWriter(file)) { | ||
| out.print(text); | ||
| } | ||
| } | ||
|
|
||
| private void putConfigInHome(JenkinsRule r, String config) throws Exception { | ||
| File configFile = new File(r.getWebAppRoot(), ConfigurationAsCode.DEFAULT_JENKINS_YAML_PATH); | ||
|
|
||
| writeToFile(config, configFile.getAbsolutePath()); | ||
| assertTrue(configFile.exists(), ConfigurationAsCode.DEFAULT_JENKINS_YAML_PATH + " should be created"); | ||
| } | ||
|
|
||
| private void assertConfigViaWebUI(JenkinsRule r, String jenkinsConfig) throws Exception { | ||
| // The UI requires the path to the config file | ||
| File f = File.createTempFile("junit", null, tempFolder.toFile()); | ||
| writeToFile(jenkinsConfig, f.getAbsolutePath()); | ||
|
|
||
| // Call the check url | ||
| JenkinsRule.WebClient client = r.createWebClient(); | ||
| WebRequest request = new WebRequest(client.createCrumbedUrl("configuration-as-code/checkNewSource"), POST); | ||
| NameValuePair param = new NameValuePair("newSource", f.toURI().toURL().toExternalForm()); | ||
| request.setRequestParameters(Collections.singletonList(param)); | ||
| WebResponse response = client.loadWebResponse(request); | ||
| assertEquals( | ||
| 200, | ||
| response.getStatusCode(), | ||
| "Failed to POST to " + request.getUrl().toString()); | ||
| String res = response.getContentAsString(); | ||
| assertThat(res, containsString("The configuration can be applied")); | ||
| } | ||
|
|
||
| private void applyConfigViaWebUI(JenkinsRule r, String jenkinsConfig) throws Exception { | ||
| // The UI requires the path to the config file | ||
| File f = File.createTempFile("junit", null, tempFolder.toFile()); | ||
| writeToFile(jenkinsConfig, f.getAbsolutePath()); | ||
|
|
||
| // Call the replace url | ||
| JenkinsRule.WebClient client = r.createWebClient(); | ||
| WebRequest request = new WebRequest(client.createCrumbedUrl("configuration-as-code/replace"), POST); | ||
| NameValuePair param = new NameValuePair("_.newSource", f.toURI().toURL().toExternalForm()); | ||
| request.setRequestParameters(Collections.singletonList(param)); | ||
| request.setRequestParameters(Collections.singletonList(param)); | ||
| WebResponse response = client.loadWebResponse(request); | ||
| assertEquals( | ||
| 200, | ||
| response.getStatusCode(), | ||
| "Failed to POST to " + request.getUrl().toString()); | ||
| String res = response.getContentAsString(); | ||
| /* The result page has: | ||
| Configuration loaded from : | ||
| <ul> | ||
| <li>path</li> | ||
| </ul> | ||
| path is the file used to store the configuration. | ||
| */ | ||
| assertThat(res, containsString(f.toURI().toURL().toExternalForm())); | ||
| } | ||
|
|
||
| private void assertLogAsExpected(LogRecorder recorder, String uniqueText) { | ||
| assertTrue( | ||
| recorder.getMessages().stream().anyMatch(m -> m.contains(uniqueText)), | ||
| "The log should have '" + uniqueText + "'"); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is optional, but I'd like to make users aware of the alternative.