add tests for ScriptRunner and GitRunner, major moving around of logic
							parent
							
								
									a70ae91ce4
								
							
						
					
					
						commit
						928a08b842
					
				| @ -0,0 +1,22 @@ | |||||||
|  | package alfred.web.core | ||||||
|  | 
 | ||||||
|  | import alfred.web.core.build.BuildId | ||||||
|  | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.nio.file.Paths | ||||||
|  | 
 | ||||||
|  | @Service | ||||||
|  | class AlfredHome( | ||||||
|  |     @Value("\${ALFRED_HOME}") | ||||||
|  |     private val home: String, | ||||||
|  | ) { | ||||||
|  | 
 | ||||||
|  |     val homePath = Paths.get(home) | ||||||
|  | 
 | ||||||
|  |     fun logsDir() = | ||||||
|  |         homePath.resolve("logs") | ||||||
|  | 
 | ||||||
|  |     fun buildConfig(buildId: BuildId) = | ||||||
|  |         homePath.resolve("builds/${buildId}.properties") | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,64 @@ | |||||||
|  | package alfred.web.core.process | ||||||
|  | 
 | ||||||
|  | import alfred.web.core.Handle | ||||||
|  | import alfred.web.core.Handles | ||||||
|  | import alfred.web.core.build.BuildContext | ||||||
|  | import alfred.web.core.build.BuildId | ||||||
|  | import alfred.web.core.build.Workspace | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.util.concurrent.TimeUnit | ||||||
|  | 
 | ||||||
|  | @Service | ||||||
|  | class Git( | ||||||
|  |     val processes: Processes, | ||||||
|  |     val handles: Handles, | ||||||
|  | ) { | ||||||
|  | 
 | ||||||
|  |     fun clone(ctx: BuildContext, ws: Workspace) { | ||||||
|  |         ctx.log("cloning ${ctx.config.gitRepo} into $ws") | ||||||
|  | 
 | ||||||
|  |         val proc = processes.builder(ctx.config, ctx.logFile, "") | ||||||
|  |             .command("git", "clone", ctx.config.gitRepo, ".") | ||||||
|  |             .directory(ws.toFile()) | ||||||
|  |             .start() | ||||||
|  |         handles.add(Handle(proc.toHandle(), ctx.buildId)) | ||||||
|  | 
 | ||||||
|  |         val cloneSuccess = proc.waitFor(ctx.config.gitCloneTimeout, TimeUnit.SECONDS) | ||||||
|  |         if (!cloneSuccess) { | ||||||
|  |             throw FailedToClone(ctx.buildId, ctx.config.gitRepo ?: "[no repo configured]") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun checkout(ctx: BuildContext, ws: Workspace) { | ||||||
|  |         ctx.log("checkout rev ${ctx.rev}") | ||||||
|  | 
 | ||||||
|  |         val proc = processes.builder(ctx.config, ctx.logFile, "") | ||||||
|  |             .command("git", "checkout", ctx.rev) | ||||||
|  |             .directory(ws.toFile()) | ||||||
|  |             .start() | ||||||
|  |         handles.add(Handle(proc.toHandle(), ctx.buildId)) | ||||||
|  | 
 | ||||||
|  |         val checkoutSuccess = proc.waitFor(ctx.config.gitCloneTimeout, TimeUnit.SECONDS) | ||||||
|  |         if (!checkoutSuccess) { | ||||||
|  |             throw FailedToCheckout(ctx.buildId, ctx.config.gitRepo!!, ctx.rev) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     sealed class GitException(msg: String): Exception(msg) | ||||||
|  | 
 | ||||||
|  |     private class FailedToClone( | ||||||
|  |         buildId: BuildId, | ||||||
|  |         gitRepo: String | ||||||
|  |     ) : GitException( | ||||||
|  |         "failed to clone $gitRepo for build id $buildId" | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     private class FailedToCheckout( | ||||||
|  |         buildId: BuildId, | ||||||
|  |         gitRepo: String, | ||||||
|  |         rev: String | ||||||
|  |     ) : GitException( | ||||||
|  |         "failed to checkout revision $rev on repo $gitRepo for build id $buildId" | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,34 @@ | |||||||
|  | package alfred.web.core.process | ||||||
|  | 
 | ||||||
|  | import alfred.web.core.Handle | ||||||
|  | import alfred.web.core.Handles | ||||||
|  | import alfred.web.core.build.BuildContext | ||||||
|  | import alfred.web.core.build.Workspace | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.io.File | ||||||
|  | 
 | ||||||
|  | @Service | ||||||
|  | class Script( | ||||||
|  |     val handles: Handles, | ||||||
|  |     val processes: Processes | ||||||
|  | ) { | ||||||
|  | 
 | ||||||
|  |     fun execute(ctx: BuildContext, ws: Workspace, scriptFile: File): Process { | ||||||
|  |         ctx.log("Running build file: ${scriptFile.name}") | ||||||
|  | 
 | ||||||
|  |         val scriptProcess = processes.builder(ctx.config, ctx.logFile, ctx.rev) | ||||||
|  |             .command(scriptFile.absolutePath) | ||||||
|  |             .directory(ws.toFile()) | ||||||
|  |             .start() | ||||||
|  |         handles.add(Handle(scriptProcess.toHandle(), ctx.buildId)) | ||||||
|  | 
 | ||||||
|  |         return scriptProcess | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun exists(scriptFile: File) = scriptFile.exists() | ||||||
|  | 
 | ||||||
|  |     fun onExit(process: Process, block: () -> Unit) { | ||||||
|  |         process.onExit().whenComplete { _, _ -> block() } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,12 @@ | |||||||
|  | package alfred.web.core | ||||||
|  | 
 | ||||||
|  | import io.mockk.justRun | ||||||
|  | import io.mockk.mockk | ||||||
|  | import org.springframework.context.ApplicationEventPublisher | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  | 
 | ||||||
|  | fun <T : Any> eventPublisher(eventType: KClass<T>): ApplicationEventPublisher { | ||||||
|  |     val eventPublisher = mockk<ApplicationEventPublisher>() | ||||||
|  |     justRun { eventPublisher.publishEvent(any(eventType)) } | ||||||
|  |     return eventPublisher | ||||||
|  | } | ||||||
| @ -0,0 +1,73 @@ | |||||||
|  | package alfred.web.core.runner | ||||||
|  | 
 | ||||||
|  | import alfred.web.core.AlfredHome | ||||||
|  | import alfred.web.core.build.Builds | ||||||
|  | import alfred.web.core.build.Workspaces | ||||||
|  | import alfred.web.core.event.BuildFinished | ||||||
|  | import alfred.web.core.eventPublisher | ||||||
|  | import alfred.web.core.process.Git | ||||||
|  | import alfred.web.core.process.ProcessEnvironment | ||||||
|  | import alfred.web.core.process.Processes | ||||||
|  | import alfred.web.core.process.Script | ||||||
|  | import io.mockk.every | ||||||
|  | import io.mockk.justRun | ||||||
|  | import io.mockk.mockk | ||||||
|  | import io.mockk.spyk | ||||||
|  | import io.mockk.verify | ||||||
|  | import org.junit.jupiter.api.Test | ||||||
|  | import org.junit.jupiter.api.io.TempDir | ||||||
|  | import java.nio.file.Path | ||||||
|  | import java.nio.file.Paths | ||||||
|  | 
 | ||||||
|  | class GitRunnerTest { | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     fun runs(@TempDir logsDir: Path) { | ||||||
|  |         // Given | ||||||
|  |         val eventPublisher = eventPublisher(BuildFinished::class) | ||||||
|  | 
 | ||||||
|  |         val git = mockk<Git>() | ||||||
|  |         justRun { git.clone(any(), any()) } | ||||||
|  |         justRun { git.checkout(any(), any()) } | ||||||
|  | 
 | ||||||
|  |         val script = mockk<Script>() | ||||||
|  |         every { script.execute(any(), any(), any()) } returns (mockk<Process>(relaxed = true)) | ||||||
|  |         every { script.exists(any()) } returns (true) | ||||||
|  | 
 | ||||||
|  |         val workspacesSpy = spyk(Workspaces()) | ||||||
|  | 
 | ||||||
|  |         val homeDir = Paths.get("").toAbsolutePath().resolve("src/test/resources/home-1") | ||||||
|  |         val home = spyk(AlfredHome(homeDir.toFile().absolutePath)) | ||||||
|  |         every { home.logsDir() } returns logsDir | ||||||
|  | 
 | ||||||
|  |         val runner = GitRunner( | ||||||
|  |             builds = Builds(home), | ||||||
|  |             processes = Processes(ProcessEnvironment()), | ||||||
|  |             workspaces = workspacesSpy, | ||||||
|  |             eventPublisher = eventPublisher, | ||||||
|  |             git = git, | ||||||
|  |             script = script | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         // When | ||||||
|  |         runner.run( | ||||||
|  |             buildId = "simple-git", | ||||||
|  |             rev = "master" | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         // Then | ||||||
|  |         verify(timeout = 1_000, exactly = 1) { | ||||||
|  |             git.clone(any(), any()) | ||||||
|  |             git.checkout(any(), any()) | ||||||
|  | 
 | ||||||
|  |             listOf("pre.sh", "job.sh", "post.sh").forEach { scriptName -> | ||||||
|  |                 script.exists(match { it.name == scriptName }) | ||||||
|  |                 script.execute(any(), any(), match { it.name == scriptName }) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             eventPublisher.publishEvent(any(BuildFinished::class)) | ||||||
|  |             workspacesSpy.cleanUp(any()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,65 @@ | |||||||
|  | package alfred.web.core.runner | ||||||
|  | 
 | ||||||
|  | import alfred.web.core.AlfredHome | ||||||
|  | import alfred.web.core.build.Builds | ||||||
|  | import alfred.web.core.build.Workspaces | ||||||
|  | import alfred.web.core.event.BuildFinished | ||||||
|  | import alfred.web.core.eventPublisher | ||||||
|  | import alfred.web.core.process.Script | ||||||
|  | import io.mockk.every | ||||||
|  | import io.mockk.mockk | ||||||
|  | import io.mockk.slot | ||||||
|  | import io.mockk.spyk | ||||||
|  | import io.mockk.verify | ||||||
|  | import org.junit.jupiter.api.Test | ||||||
|  | import org.junit.jupiter.api.io.TempDir | ||||||
|  | import org.springframework.test.web.client.ExpectedCount.once | ||||||
|  | import java.nio.file.Path | ||||||
|  | import java.nio.file.Paths | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ScriptRunnerTest { | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     fun runs(@TempDir logDir: Path) { | ||||||
|  |         // Given | ||||||
|  |         val home = spyk( | ||||||
|  |             AlfredHome( | ||||||
|  |                 Paths.get("").toAbsolutePath().resolve("src/test/resources/home-1").toString() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |         every { home.logsDir() } returns logDir | ||||||
|  | 
 | ||||||
|  |         val eventPublisher = eventPublisher(BuildFinished::class) | ||||||
|  | 
 | ||||||
|  |         val script = mockk<Script>() | ||||||
|  |         every { script.execute(any(), any(), any()) } returns mockk<Process>(relaxed = true) | ||||||
|  |         val onExit = slot<() -> Unit>() | ||||||
|  |         every { script.onExit(any(), capture(onExit)) } answers { onExit.captured() } | ||||||
|  | 
 | ||||||
|  |         val workspaces = spyk(Workspaces()) | ||||||
|  | 
 | ||||||
|  |         val runner = ScriptRunner( | ||||||
|  |             builds = Builds(home), | ||||||
|  |             workspaces = workspaces, | ||||||
|  |             eventPublisher = eventPublisher, | ||||||
|  |             script = script | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         // When | ||||||
|  |         runner.run( | ||||||
|  |             buildId = "simple-script", | ||||||
|  |             rev = null | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         // Then | ||||||
|  |         verify(timeout = 1_000, exactly = 1) { | ||||||
|  |             script.execute(any(), any(), match { it.name == "some-script" }) | ||||||
|  | 
 | ||||||
|  |             workspaces.cleanUp(any()) | ||||||
|  | 
 | ||||||
|  |             eventPublisher.publishEvent(any(BuildFinished::class)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | user=alfred | ||||||
|  | workspace=/tmp | ||||||
|  | apikey=Mellon | ||||||
|  | 
 | ||||||
|  | git.repo.url=some.url | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | user=alfred | ||||||
|  | workspace=/tmp | ||||||
|  | apikey=Mellon | ||||||
|  | 
 | ||||||
|  | script=some-script | ||||||
					Loading…
					
					
				
		Reference in New Issue
	
	 Josha von Gizycki
						Josha von Gizycki