public static async Task RunAndAssertAppAsync( string imageName, ITestOutputHelper output, IEnumerable <DockerVolume> volumes, List <EnvironmentVariable> environmentVariables, int port, string link, string runCmd, string[] runArgs, Func <int, Task> assertAction, DockerCli dockerCli) { DockerRunCommandProcessResult runResult = null; var showDebugInfo = true; try { // Docker run the runtime container as a foreground process. This way we can catch any errors // that might occur when the application is being started. runResult = dockerCli.RunAndDoNotWaitForProcessExit(new DockerRunArguments { ImageId = imageName, EnvironmentVariables = environmentVariables, Volumes = volumes, PortInContainer = port, Link = link, CommandToExecuteOnRun = runCmd, CommandArguments = runArgs, }); // An exception could have occurred when a Docker process failed to start. Assert.Null(runResult.Exception); Assert.False(runResult.Process.HasExited); var hostPort = await GetHostPortAsync(dockerCli, runResult.ContainerName, portInContainer : port); for (var i = 0; i < MaxRetryCount; i++) { try { // Make sure the process is still alive and fail fast if not. Assert.False(runResult.Process.HasExited); await assertAction(hostPort); showDebugInfo = false; break; } catch (Exception ex) when(ex.InnerException is IOException || ex.InnerException is SocketException) { if (i == MaxRetryCount - 1) { throw; } await Task.Delay(DelayBetweenRetries); } } } finally { if (runResult != null) { // Stop the container so that shared resources (like ports) are disposed. dockerCli.StopContainer(runResult.ContainerName); // Access the output and error streams after the process has exited if (showDebugInfo) { output.WriteLine(runResult.GetDebugInfo()); } } } }
// The sequence of steps are: // 1. Copies the sample app from host machine's git repo to a different folder (under 'tmp/<reserved-name>' folder // in CI machine and in the 'bin\debug\' folder in non-CI machine). The copying is necessary to not muck // with git tracked samples folder and also a single sample could be used for verification in multiple tests. // 2. Volume mounts the directory to the build image and build it. // 3. Volume mounts the same directory to runtime image and runs the application. // 4. A func supplied by the user is retried to the max of 10 retries between a delay of 1 second. public static async Task BuildRunAndAssertAppAsync( ITestOutputHelper output, DockerVolume volume, string buildCmd, string[] buildArgs, string runtimeImageName, List <EnvironmentVariable> environmentVariables, string portMapping, string link, string runCmd, string[] runArgs, Func <Task> assertAction) { var dockerCli = new DockerCli(); // Build var buildAppResult = dockerCli.Run( Settings.BuildImageName, volume, commandToExecuteOnRun: buildCmd, commandArguments: buildArgs); await RunAssertsAsync( () => { Assert.True(buildAppResult.IsSuccess); return(Task.CompletedTask); }, buildAppResult.GetDebugInfo()); // Run DockerRunCommandProcessResult runResult = null; try { // Docker run the runtime container as a foreground process. This way we can catch any errors // that might occur when the application is being started. runResult = dockerCli.RunAndDoNotWaitForProcessExit( runtimeImageName, environmentVariables, volumes: new List <DockerVolume> { volume }, portMapping, link, runCmd, runArgs); await RunAssertsAsync( () => { // An exception could have occurred when a docker process failed to start. Assert.Null(runResult.Exception); Assert.False(runResult.Process.HasExited); return(Task.CompletedTask); }, runResult.GetDebugInfo()); for (var i = 0; i < MaxRetryCount; i++) { await Task.Delay(TimeSpan.FromSeconds(DelayBetweenRetriesInSeconds)); try { // Make sure the process is still alive and fail fast if not alive. await RunAssertsAsync( async() => { Assert.False(runResult.Process.HasExited); await assertAction(); }, runResult.GetDebugInfo()); break; } catch (Exception ex) when(ex.InnerException is IOException || ex.InnerException is SocketException) { if (i == MaxRetryCount - 1) { output.WriteLine(runResult.GetDebugInfo()); throw; } } } } finally { if (runResult != null && runResult.Exception == null) { // Stop the container so that shared resources (like ports) are disposed. dockerCli.StopContainer(runResult.ContainerName); } } async Task RunAssertsAsync(Func <Task> action, string message) { try { await action(); } catch (Exception) { output.WriteLine(message); throw; } } }