From 124f37a492a86e7dec645e5d46ecb410431b5941 Mon Sep 17 00:00:00 2001 From: Colin C Date: Fri, 26 Feb 2016 09:23:02 -0800 Subject: [PATCH] Add Tests Initial tests. --- .../marathon/MarathonRecorderTest.java | 272 ++++++++++++++++++ .../impl/MarathonBuilderImplTest.java | 116 ++++++++ 2 files changed, 388 insertions(+) create mode 100644 src/test/java/com/mesosphere/velocity/marathon/MarathonRecorderTest.java create mode 100644 src/test/java/com/mesosphere/velocity/marathon/impl/MarathonBuilderImplTest.java diff --git a/src/test/java/com/mesosphere/velocity/marathon/MarathonRecorderTest.java b/src/test/java/com/mesosphere/velocity/marathon/MarathonRecorderTest.java new file mode 100644 index 0000000..7bf315f --- /dev/null +++ b/src/test/java/com/mesosphere/velocity/marathon/MarathonRecorderTest.java @@ -0,0 +1,272 @@ +package com.mesosphere.velocity.marathon; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import hudson.Launcher; +import hudson.model.*; +import hudson.tasks.Shell; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestBuilder; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class MarathonRecorderTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + /** + * An HTTP Server to receive requests from the plugin. + */ + HttpServer httpServer; + TestHandler handler; + InetSocketAddress serverAddress; + + @Before + public void setUp() throws IOException { + handler = new TestHandler(); + serverAddress = new InetSocketAddress("localhost", 0); + + httpServer = HttpServer.create(serverAddress, 500); + httpServer.createContext("/", handler); + httpServer.start(); + } + + @After + public void tearDown() { + httpServer.stop(0); + } + + /** + * Test that when "marathon.json" is not present the build is failed + * and no requests are made to the configured Marathon instance. + * + * @throws Exception + */ + @Test + public void testRecorderNoFile() throws Exception { + final FreeStyleProject project = j.createFreeStyleProject(); + project.getBuildersList().add(new Shell("echo hello")); + + // add recorder + project.getPublishersList().add(new MarathonRecorder(getHttpAddresss())); + + // run a build with the shell step and recorder publisher + final FreeStyleBuild build = project.scheduleBuild2(0).get(); + + // get console log + final String s = FileUtils.readFileToString(build.getLogFile()); + + // assert things + assertEquals("Build should fail", Result.FAILURE, build.getResult()); + assertTrue(s.contains("[Marathon]")); + assertTrue(s.contains("marathon.json")); + assertEquals("No web requests were made", 0, handler.getRequestCount()); + } + + /** + * Test a basic successful scenario. The Marathon instance will return + * a 200 OK. + * + * @throws Exception + */ + @Test + public void testRecorderPass() throws Exception { + final String payload = "{\"id\":\"myapp\"}"; + final FreeStyleProject project = j.createFreeStyleProject(); + + // add builders + project.getBuildersList().add(new Shell("echo hello")); + project.getBuildersList().add(createMarathonFileBuilder(payload)); + + // add post-builder + project.getPublishersList().add(new MarathonRecorder(getHttpAddresss())); + + // run a build with the shell step and recorder publisher + final FreeStyleBuild build = project.scheduleBuild2(0).get(); + + // get console log + final String s = FileUtils.readFileToString(build.getLogFile()); + + // assert things + assertEquals("Build should fail", Result.SUCCESS, build.getResult()); + assertTrue(s.contains("[Marathon]")); + assertTrue(s.contains("application updated")); + assertEquals("Only 1 web request", 1, handler.getRequestCount()); + } + + private TestBuilder createMarathonFileBuilder(final String payload) { + return new TestBuilder() { + public boolean perform(AbstractBuild build, Launcher launcher, + BuildListener listener) throws InterruptedException, IOException { + build.getWorkspace().child("marathon.json").write(payload, "UTF-8"); + return true; + } + }; + } + + /** + * Test that a 409 response from the Marathon instance triggers + * retry logic. The default logic is to try 3 times with X + * seconds in between each request. + * + * @throws Exception + */ + @Test + public void testRecorderMaxRetries() throws Exception { + final String payload = "{\"id\":\"myapp\"}"; + final FreeStyleProject project = j.createFreeStyleProject(); + + // add builders + project.getBuildersList().add(new Shell("echo hello")); + project.getBuildersList().add(createMarathonFileBuilder(payload)); + + // add post-builder + project.getPublishersList().add(new MarathonRecorder(getHttpAddresss())); + + // return 409 to trigger retry logic + handler.setResponseCode(409); + + // run a build with the shell step and recorder publisher + final FreeStyleBuild build = project.scheduleBuild2(0).get(); + + // get console log + final String s = FileUtils.readFileToString(build.getLogFile()); + + // assert things + assertEquals("Build should fail", Result.FAILURE, build.getResult()); + assertTrue(s.contains("[Marathon]")); + assertTrue(s.contains("max retries")); + assertEquals("Should be 3 retries", 3, handler.getRequestCount()); + } + + /** + * Test that a 4xx (404 in this case) response code does not + * trigger retries. This should result in only one request + * being made to the configured Marathon instance. + * + * @throws Exception + */ + @Test + public void testRecorder404() throws Exception { + final String payload = "{\"id\":\"myapp\"}"; + final FreeStyleProject project = j.createFreeStyleProject(); + + // add builders + project.getBuildersList().add(new Shell("echo hello")); + project.getBuildersList().add(createMarathonFileBuilder(payload)); + + // add post-builder + project.getPublishersList().add(new MarathonRecorder(getHttpAddresss())); + + // return a 404, which will fail the build + handler.setResponseCode(404); + + // run a build with the shell step and recorder publisher + final FreeStyleBuild build = project.scheduleBuild2(0).get(); + + // get console log + final String s = FileUtils.readFileToString(build.getLogFile()); + + // assert things + assertEquals("Build should fail", Result.FAILURE, build.getResult()); + assertTrue(s.contains("[Marathon]")); + assertTrue(s.contains("Failed to update")); + assertEquals("Only 1 request should be made", 1, handler.getRequestCount()); + } + + /** + * Test that a 5xx (503 in this case) response code does not + * trigger retries. This should result in only one request + * being made to the configured Marathon instance. + * + * @throws Exception + */ + @Test + public void testRecorder503() throws Exception { + final String payload = "{\"id\":\"myapp\"}"; + final FreeStyleProject project = j.createFreeStyleProject(); + + // add builders + project.getBuildersList().add(new Shell("echo hello")); + project.getBuildersList().add(createMarathonFileBuilder(payload)); + + // add post-builder + project.getPublishersList().add(new MarathonRecorder(getHttpAddresss())); + + // return a 503, which will fail the build + handler.setResponseCode(503); + + // run a build with the shell step and recorder publisher + final FreeStyleBuild build = project.scheduleBuild2(0).get(); + + // get console log + final String s = FileUtils.readFileToString(build.getLogFile()); + + // assert things + assertEquals("Build should fail", Result.FAILURE, build.getResult()); + assertTrue(s.contains("[Marathon]")); + assertTrue(s.contains("Failed to update")); + assertEquals("Only 1 request should be made", 1, handler.getRequestCount()); + } + + private String getHttpAddresss() { + return "http://" + httpServer.getAddress().getHostName() + ":" + httpServer.getAddress().getPort(); + } + + /** + * An {@link HttpHandler} that counts the number of requests. + * The response body and status code can be altered for each + * test scenario. + */ + class TestHandler implements HttpHandler { + private int requestCount; + private String responseBody; + private int responseCode; + + public TestHandler() { + this.requestCount = 0; + this.responseBody = null; + this.responseCode = 200; + } + + @Override + public void handle(HttpExchange httpExchange) throws IOException { + requestCount++; + httpExchange.sendResponseHeaders(responseCode, responseBody != null ? responseBody.length() : 0); + } + + public int getRequestCount() { + return requestCount; + } + + public String getResponseBody() { + return responseBody; + } + + public void setResponseBody(String responseBody) { + this.responseBody = responseBody; + } + + public int getResponseCode() { + return responseCode; + } + + public void setResponseCode(int responseCode) { + this.responseCode = responseCode; + } + + public void resetRequestCount() { + this.requestCount = 0; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/mesosphere/velocity/marathon/impl/MarathonBuilderImplTest.java b/src/test/java/com/mesosphere/velocity/marathon/impl/MarathonBuilderImplTest.java new file mode 100644 index 0000000..3592e13 --- /dev/null +++ b/src/test/java/com/mesosphere/velocity/marathon/impl/MarathonBuilderImplTest.java @@ -0,0 +1,116 @@ +package com.mesosphere.velocity.marathon.impl; + +import com.mesosphere.velocity.marathon.exceptions.MarathonFileInvalidException; +import com.mesosphere.velocity.marathon.exceptions.MarathonFileMissingException; +import com.mesosphere.velocity.marathon.interfaces.AppConfig; +import com.mesosphere.velocity.marathon.interfaces.MarathonBuilder; +import hudson.FilePath; +import net.sf.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.IOException; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({FilePath.class}) +public class MarathonBuilderImplTest { + @Rule + public final ExpectedException exception = ExpectedException.none(); + @Mock + private AppConfig appConfig; + private MarathonBuilder builder; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testSetJson() { + final String key = "key"; + final String val = "testvalue"; + final JSONObject json = new JSONObject(); + + builder = new MarathonBuilderImpl(appConfig); + assertNull("JSON is null on initial creation.", builder.getJson()); + + builder.setJson(json); + assertNotNull("An empty JSON object is set correctly.", builder.getJson()); + + json.put(key, val); + builder.setJson(json); + assertEquals("Simple JSON object with values is set correctly.", builder.getJson().getString(key), val); + } + + @Test + public void testReadNonexistingFile() + throws IOException, InterruptedException, MarathonFileMissingException, MarathonFileInvalidException { + final String filename = "somefile"; + final FilePath wsMock = PowerMockito.mock(FilePath.class); + final FilePath fileMock = PowerMockito.mock(FilePath.class); + when(wsMock.child(anyString())).thenReturn(fileMock); + when(fileMock.exists()).thenReturn(false); + + // create builder + builder = new MarathonBuilderImpl(appConfig); + + // setup FileMissing exception + exception.expect(MarathonFileMissingException.class); + builder.setWorkspace(wsMock).read(filename); + } + + @Test + public void testReadDirectoryFile() throws InterruptedException, MarathonFileMissingException, MarathonFileInvalidException, IOException { + final String filename = "somefile"; + final FilePath wsMock = PowerMockito.mock(FilePath.class); + final FilePath fileMock = PowerMockito.mock(FilePath.class); + when(wsMock.child(anyString())).thenReturn(fileMock); + when(fileMock.exists()).thenReturn(true); + when(fileMock.isDirectory()).thenReturn(true); + + // create builder + builder = new MarathonBuilderImpl(appConfig); + + // setup FileInvalid exception + exception.expect(MarathonFileInvalidException.class); + builder.setWorkspace(wsMock).read(filename); + } + + @Test + public void testReadPositive() throws Exception { + final String filename = "somefile"; + final FilePath wsMock = PowerMockito.mock(FilePath.class); + final FilePath fileMock = PowerMockito.mock(FilePath.class); + final JSONObject expectedJson = new JSONObject(); + + when(wsMock.child(anyString())).thenReturn(fileMock); + when(fileMock.exists()).thenReturn(true); + when(fileMock.isDirectory()).thenReturn(false); + + // the magic... + when(fileMock.readToString()).thenReturn("{}"); + + builder = new MarathonBuilderImpl(appConfig); + builder.setWorkspace(wsMock).read(filename); + assertEquals("Empty JSON Object was read in", expectedJson, builder.getJson()); + + // now non-empty + when(fileMock.readToString()).thenReturn("{\"id\": \"myid\"}"); + expectedJson.put("id", "myid"); + builder.read(filename); + assertEquals("JSON should have same id", + expectedJson.getString("id"), builder.getJson().getString("id")); + } +} \ No newline at end of file