Skip to content
This repository has been archived by the owner on Jul 19, 2024. It is now read-only.

use Convert-ToDockerHostPath for docker tests #117

Merged
merged 10 commits into from
Dec 17, 2020
31 changes: 31 additions & 0 deletions Source/Private/Convert-ToDockerHostPath.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
function Convert-ToDockerHostPath {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[String]
$Path
)

$pathOnDockerHost = $Path
$commandResultPS = Invoke-DockerCommand 'ps' -Quiet
Assert-ExitCodeOK $commandResultPS

if ( ($commandResultPS).StdOut | Select-String $(hostname) ) {
# executed inside docker container $(hostname)
$dockerCommand = "inspect -f ""{{ range .Mounts }}{{ .Source }}={{ .Destination }}{{ println }} {{ end }}"" $(hostname)"
$commandResultInspect = Invoke-DockerCommand $dockerCommand -Quiet
Assert-ExitCodeOK $commandResultInspect

$mounts = ($commandResultInspect).StdOut.trim() | Where-Object { $_ -NotMatch "/var/lib/docker" -and $_ -NotMatch "docker.sock" -and $_ -NotMatch "\\pipe\\" -and $_ -ne '' }

if ($mounts.Length -gt 0) {
$mounts | ForEach-Object {
if ($_.split('=')[0] -ne $_.split('=')[1]) {
# Replace container path with host path
$pathOnDockerHost = $pathOnDockerHost.Replace($_.split('=')[1], $_.split('=')[0])
}
}
}
}
return $pathOnDockerHost
}
6 changes: 4 additions & 2 deletions Source/Public/Invoke-DockerTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ function Invoke-DockerTests {
}

$here = Format-AsAbsolutePath (Get-Location)
$hereOnDockerHost = Convert-ToDockerHostPath $here
$absoluteTestReportDir = Format-AsAbsolutePath ($TestReportDir)
if (!(Test-Path $absoluteTestReportDir -PathType Container)) {
New-Item $absoluteTestReportDir -ItemType Directory -Force | Out-Null
}
$absoluteTestReportDirOnDockerHost = Convert-ToDockerHostPath $absoluteTestReportDir
$osType = Find-DockerOSType
$dockerSocket = Find-DockerSocket -OsType $osType
if ($osType -ieq 'windows') {
Expand All @@ -45,8 +47,8 @@ function Invoke-DockerTests {
$report = '/report'
}
$structureCommand = "run -i" + `
" -v `"${here}:${configs}`"" + `
" -v `"${absoluteTestReportDir}:${report}`"" + `
" -v `"${hereOnDockerHost}:${configs}`"" + `
" -v `"${absoluteTestReportDirOnDockerHost}:${report}`"" + `
" -v `"${dockerSocket}:${dockerSocket}`"" + `
" 3shape/containerized-structure-test:latest test -i ${ImageName} --test-report ${report}/${TestReportName}"

Expand Down
121 changes: 121 additions & 0 deletions Test-Source/Convert-ToDockerHostPath.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
Import-Module -Force (Get-ChildItem -Path $PSScriptRoot/../Source -Recurse -Include *.psm1 -File).FullName
Import-Module -Global -Force $PSScriptRoot/Docker-CI.Tests.psm1
. "$PSScriptRoot\..\Source\Private\Convert-ToDockerHostPath.ps1"

Describe 'Convert absolute path to path on docker host with Convert-ToDockerHostPath' {

BeforeEach {
Initialize-MockReg
}

InModuleScope $Global:ModuleName {

Context 'Verify we can mock' {

It 'Hostname can be mocked' {
Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable
$result = hostname
$result | Should -Be $Global:DockerContainerHostname
Assert-MockCalled -CommandName "hostname" -Times 1
}

It 'Invoke-DockerCommand can be mocked (ps)' {
Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
$result = Invoke-DockerCommand 'ps'
$result.Output | Should -Be $Global:DockerPsOutput
Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
}

It 'Invoke-DockerCommand can be mocked (inspect)' {
Mock -CommandName "Invoke-DockerCommand" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable
$result = Invoke-DockerCommand 'inspect -f'
$result.Output | Should -Be $Global:DockerInspectOutput
Assert-MockCalled -CommandName "Invoke-DockerCommand" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1
}
}

Context 'When run NOT inside docker container' {

It 'returns unchanged provided path' {
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
Mock -CommandName "hostname" -MockWith { return 'someHostname' } -Verifiable
$result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath
$result | Should -Be $Global:WorkspaceAbsolutePath
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
Assert-MockCalled -CommandName "hostname" -Times 1
}
}

Context 'When run inside docker container' {

It 'returns the path on docker host if folder belongs to mapped volume generic' {
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerInspectMockCode -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable
$result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath
$result | Should -Be $Global:DockerHostAbsolutePath
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
Assert-MockCalled -CommandName "hostname" -Times 1
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1
}

if ($IsWindows) {
It 'returns the path on docker host if folder belongs to mapped volume on Windows' {
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerInspectMockCodeWindows -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable
$result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath
$result | Should -Be $Global:DockerHostAbsolutePath
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
Assert-MockCalled -CommandName "hostname" -Times 1
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1
}
}

if ($IsLinux) {
It 'returns the path on docker host if folder belongs to mapped volume on Linux' {
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerInspectMockCodeLinux -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable
$result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath
$result | Should -Be $Global:DockerHostAbsolutePath
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
Assert-MockCalled -CommandName "hostname" -Times 1
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1
}
}

It 'returns the provided path otherwise' {
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable
Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeZeroAndEmptyStdOut -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable
$result = Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath
$result | Should -Be $Global:WorkspaceAbsolutePath
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 2
Assert-MockCalled -CommandName "hostname" -Times 2
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 2
}
}

Context 'When docker ps or docker inspect command fails' {

It 'cannot proceed further when docker ps command fails, throws exception on non-zero exit code' {
Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
$theCode = { Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath }
$theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
}

It 'cannot proceed further when docker inspect command fails, throws exception on non-zero exit code' {
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable
Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable
$theCode = { Convert-ToDockerHostPath $Global:WorkspaceAbsolutePath }
$theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
Assert-MockCalled -CommandName "hostname" -Times 2
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1
}
}
}
}
70 changes: 70 additions & 0 deletions Test-Source/Docker-CI.Tests.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,76 @@ Set-GlobalVar -Variable CodeThatReturnsExitCodeOne -Value {
return $result
}

Set-GlobalVar -Variable CodeThatReturnsExitCodeZeroAndEmptyStdOut -Value {
$result = [CommandResult]::new()
$result.StdOut = ''
$result.ExitCode = 0
return $result
}

Set-GlobalVar -Variable DockerPsOutput -Value @(
'CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES',
'a28e1ca69345 jenkins.agent:bionic "jenkins-agent" 41 minutes ago Up 41 minutes jenkins.agent'
)

Set-GlobalVar -Variable DockerPsMockCode -Value {
$result = [CommandResult]::new()
$result.Output = $Global:DockerPsOutput
$result.StdOut = $Global:DockerPsOutput
$result.ExitCode = 0
return $result
}

Set-GlobalVar -Variable DockerInspectOutputWindows -Value @(
' c:\devops\workspace=c:\jenkins\workspace',
' \\.\pipe\docker_engine=\\.\pipe\docker_engine'
)

Set-GlobalVar -Variable DockerInspectOutputLinux -Value @(
' /home/devops/workspace=/home/jenkins/workspace',
' /var/run/docker.sock=/var/run/docker.sock',
' /var/lib/docker/volumes/5c15f2a0ff099e4a6d59a863a9dfa4580e5615798987200bed2e98acd26fd3f8/_data=/home/jenkins/.jenkins',
' /var/lib/docker/volumes/b29f5bbedd3a667ee45f0dde717a2c9b8092af81e0b9da92f9499f5c10a6c12e/_data=/home/jenkins/agent'
)

Set-GlobalVar -Variable DockerInspectOutput -Value @( $Global:DockerInspectOutputLinux + $Global:DockerInspectOutputWindows )

Set-GlobalVar -Variable DockerInspectMockCode -Value {
$result = [CommandResult]::new()
$result.Output = $Global:DockerInspectOutput
$result.StdOut = $Global:DockerInspectOutput
$result.ExitCode = 0
return $result
}

Set-GlobalVar -Variable DockerContainerHostname -Value 'a28e1ca69345' # must be part of DockerPsMockCode

if ($IsWindows) {
Set-GlobalVar -Variable WorkspaceAbsolutePath -Value 'c:\jenkins\workspace\mybuild'
Set-GlobalVar -Variable DockerHostAbsolutePath -Value 'c:\devops\workspace\mybuild'

Set-GlobalVar -Variable DockerInspectMockCodeWindows -Value {
$result = [CommandResult]::new()
$result.Output = $Global:DockerInspectOutputWindows
$result.StdOut = $Global:DockerInspectOutputWindows
$result.ExitCode = 0
return $result
}

} elseif ($IsLinux) {
Set-GlobalVar -Variable WorkspaceAbsolutePath -Value '/home/jenkins/workspace/mybuild'
Set-GlobalVar -Variable DockerHostAbsolutePath -Value '/home/devops/workspace/mybuild'

Set-GlobalVar -Variable DockerInspectMockCodeLinux -Value {
$result = [CommandResult]::new()
$result.Output = $Global:DockerInspectOutputLinux
$result.StdOut = $Global:DockerInspectOutputLinux
$result.ExitCode = 0
return $result
}

}

. "$PSScriptRoot\..\Source\Private\Invoke-Command.ps1"
. "$PSScriptRoot\..\Source\Private\Invoke-DockerCommand.ps1"
. "$PSScriptRoot\..\Source\Private\Assert-ExitCodeOk.ps1"
Expand Down
31 changes: 30 additions & 1 deletion Test-Source/Invoke-DockerTests.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Describe 'Run docker tests using Google Structure' {
$testResult.Results.Length | Should -Be 2
}

It 'Can pick up tests in a nested folder structure' {
It 'can pick up tests in a nested folder structure' {
$allPassingTestsDir = Join-Path $Global:StructureTestsPassDir $Global:DockerOsType
$result = Invoke-DockerTests -ImageName $imageToTest -ConfigPath $allPassingTestsDir
$commandResult = $result.CommandResult
Expand Down Expand Up @@ -168,6 +168,35 @@ Describe 'Run docker tests using Google Structure' {
$theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath (New-RandomFolder) }
$theCode | Should -Throw -ExceptionType 'System.ArgumentException'
}

InModuleScope $Global:ModuleName {

if ($IsWindows) {

It 'throws an exception if Convert-ToDockerHostPath fails on docker ps' {
$structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml'
$configs = @($structureCommandConfig)
Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
$theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs }
$theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
}

It 'throws an exception if Convert-ToDockerHostPath fails on docker inspect' {
$structureCommandConfig = Join-Path $Global:StructureTestsFailDir $Global:DockerOsType 'testshell.yml'
$configs = @($structureCommandConfig)
Mock -CommandName "Invoke-Command" -MockWith $Global:DockerPsMockCode -ParameterFilter { $CommandArgs.StartsWith('ps') } -Verifiable
Mock -CommandName "hostname" -MockWith { return $Global:DockerContainerHostname } -Verifiable
Mock -CommandName "Invoke-Command" -MockWith $Global:CodeThatReturnsExitCodeOne -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Verifiable

$theCode = { Invoke-DockerTests -ImageName $imageToTest -ConfigPath $configs }
$theCode | Should -Throw -ExceptionType ([System.Exception]) -PassThru
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('ps') } -Times 1
Assert-MockCalled -CommandName "hostname" -Times 2
Assert-MockCalled -CommandName "Invoke-Command" -ParameterFilter { $CommandArgs.StartsWith('inspect -f') } -Times 1
}
}
}
}

Context 'Pipeline execution' {
Expand Down