For those of us that write Swift Packages that run on Linux, testing them on that platform before deployment can be a good time saver. Specially when dealing with code that is available on macOS but not on Linux (e.g. Foundation vs FoundationNetworking).

Using Docker for testing Swift Packages on Linux from the command line is not a new technique. There are many articles describing how to do this. However, I find myself often searching the web for those articles (or searching across my own projects), when I forget the exact steps to set it up. So I decided to write it down here, as a blog post, and keep it handy.

A few months ago, I set this up on ProcessRunner, my little library to launch child processes from Swift. Here are the steps.

Setup Dockerfile

This setup assumes you have Docker Desktop for macOS installed

Create a Dockerfile in the root of your package (next to Package.swift)

#FROM swiftlang/swift:nightly-master-    <- use this image for the latest nightly build
#FROM swift:amazonlinux2 <- For testing on Amazon Linux 2
#FROM swift:ubuntu-latest <- For testing on Ubuntu Linux
FROM swift:latest <- default Swift image (latest)

WORKDIR /tmp

ADD Sources ./Sources
ADD Tests ./Tests
ADD Package.swift ./

CMD swift test --enable-test-discovery

The docker script above configures the file paths available in the docker Linux image, and executes the command to run the tests. If your package uses files from other directories than Sources and Tests, you will need to copy those too.

Run Tests with Docker

To run the tests you can use the docker CLI. I personally like putting this command on a Makefile, so I don’t have to remember it later on.

linuxtest:
	docker build -f Dockerfile -t linuxtest .
	docker run linuxtest

This script will build and tag the docker image, and then execute it right away.

Example output running unit tests from my Blog package on Linux. In this case my docker configuration file is called LinuxTest.Dockerfile, since I have another Dockerfile for building the lambdas:

$ make linuxtest 
docker build -f LinuxTest.Dockerfile -t linuxtest .
Sending build context to Docker daemon  460.5MB
Step 1/6 : FROM swift:amazonlinux2
 ---> ee272321cb22
Step 2/6 : WORKDIR /tmp
 ---> Using cache
 ---> 829a05bcaf38
Step 3/6 : ADD Sources ./Sources
 ---> d72020d70e2e
Step 4/6 : ADD Tests ./Tests
 ---> 5fc9bd86107f
Step 5/6 : ADD Package.swift ./
 ---> 27ba35153aa8
Step 6/6 : CMD swift test --enable-test-discovery
 ---> Running in 62ebb976560a
Removing intermediate container 62ebb976560a
 ---> 651506ce9efb
Successfully built 651506ce9efb
Successfully tagged linuxtest:latest
docker run linuxtest
Fetching https://github.com/swift-server/swift-aws-lambda-runtime
Fetching https://github.com/apple/swift-log.git
Fetching https://github.com/apple/swift-nio.git
Fetching https://github.com/swift-server/swift-backtrace.git
Cloning https://github.com/apple/swift-log.git
Resolving https://github.com/apple/swift-log.git at 1.4.0
Cloning https://github.com/swift-server/swift-aws-lambda-runtime
Resolving https://github.com/swift-server/swift-aws-lambda-runtime at 0.3.0
Cloning https://github.com/swift-server/swift-backtrace.git
Resolving https://github.com/swift-server/swift-backtrace.git at 1.2.0
Cloning https://github.com/apple/swift-nio.git
Resolving https://github.com/apple/swift-nio.git at 2.25.1
[1/36] Compiling CNIOWindows WSAStartup.c
[2/36] Compiling CNIODarwin shim.c
[3/36] Compiling CNIOWindows shim.c
[4/52] Compiling CNIOLinux shim.c
[5/52] Compiling CNIOSHA1 c_nio_sha1.c
[6/52] Compiling CBacktrace state.c
[7/52] Compiling CBacktrace sort.c
[8/52] Compiling CNIOHTTPParser c_nio_http_parser.c
[9/52] Compiling CBacktrace simple.c
[10/52] Compiling CBacktrace print.c
[11/52] Compiling CBacktrace posix.c
[12/52] Compiling CBacktrace mmapio.c
[13/52] Compiling CBacktrace mmap.c
[14/52] Compiling CBacktrace fileline.c
[15/52] Compiling CBacktrace elf.c
[16/52] Compiling CBacktrace dwarf.c
[17/52] Compiling CBacktrace backtrace.c
[18/52] Compiling CBacktrace atomic.c
[19/54] Compiling Backtrace Backtrace.swift
[20/54] Compiling Backtrace Demangle.swift
[21/55] Merging module Backtrace
[22/55] Wrapping AST for Backtrace for debugging
[24/55] Compiling AWSLambdaEvents DynamoDB.swift
[25/55] Compiling AWSLambdaEvents S3.swift
[26/55] Compiling AWSLambdaEvents SES.swift
[27/55] Compiling AWSLambdaEvents SNS.swift
[28/55] Compiling Logging Logging.swift
[32/55] Compiling AWSLambdaEvents SQS.swift
[33/55] Compiling AWSLambdaEvents Base64.swift
[35/56] Compiling AWSLambdaEvents AWSRegion.swift
[36/56] Compiling AWSLambdaEvents Cloudwatch.swift
[37/56] Compiling AWSLambdaEvents DateWrappers.swift
[38/56] Compiling AWSLambdaEvents HTTP.swift
[39/57] Merging module Logging
[40/61] Wrapping AST for Logging for debugging
[41/61] Merging module AWSLambdaEvents
[42/61] Compiling Blog IssueParser.swift
[43/61] Wrapping AST for AWSLambdaEvents for debugging
[44/61] Compiling Blog PostRenderer.swift
[45/61] Compiling Blog GitHubContext.swift
[46/61] Compiling Blog IssueProcessor.swift
[47/62] Merging module Blog
[48/64] Wrapping AST for Blog for debugging
[49/64] Compiling c-nioatomics.c
[50/64] Compiling BlogTests Event.swift
[51/64] Compiling c-atomics.c
[52/67] Compiling NIOConcurrencyHelpers NIOAtomic.swift
[53/68] Merging module BlogTests
[54/68] Wrapping AST for BlogTests for debugging
[55/68] /tmp/.build/x86_64-unknown-linux-gnu/debug/BlogPackageTestsTestlist.derived/main.swift
[56/70] Compiling BlogPackageTests BlogTests.swift
[57/70] Compiling BlogPackageTests main.swift
[58/71] Merging module BlogPackageTests
[59/71] Wrapping AST for BlogPackageTests for debugging
[60/71] Compiling NIOConcurrencyHelpers lock.swift
[61/71] Linking BlogPackageTests.xctest
[64/72] Merging module NIOConcurrencyHelpers
[65/72] Wrapping AST for NIOConcurrencyHelpers for debugging
[66/144] Compiling NIO PendingWritesManager.swift
[67/144] Compiling NIO PipeChannel.swift
[68/144] Compiling NIO PipePair.swift
[69/144] Compiling NIO PointerHelpers.swift
[70/144] Compiling NIO PriorityQueue.swift
[71/144] Compiling NIO RecvByteBufferAllocator.swift
[72/144] Compiling NIO Resolver.swift
[73/144] Compiling NIO Selectable.swift
[74/144] Compiling NIO SelectableEventLoop.swift
[75/144] Compiling NIO Selector.swift
[76/144] Compiling NIO ServerSocket.swift
[77/144] Compiling NIO SingleStepByteToMessageDecoder.swift
[78/144] Compiling NIO IOData.swift
[79/144] Compiling NIO IntegerTypes.swift
[80/144] Compiling NIO Interfaces.swift
[81/144] Compiling NIO Linux.swift
[82/144] Compiling NIO LinuxCPUSet.swift
[83/144] Compiling NIO MarkedCircularBuffer.swift
[84/144] Compiling NIO MulticastChannel.swift
[85/144] Compiling NIO NIOAny.swift
[86/144] Compiling NIO NIOCloseOnErrorHandler.swift
[87/144] Compiling NIO NIOThreadPool.swift
[88/144] Compiling NIO NonBlockingFileIO.swift
[89/144] Compiling NIO PendingDatagramWritesManager.swift
[90/144] Compiling NIO Socket.swift
[91/144] Compiling NIO SocketAddresses.swift
[92/144] Compiling NIO SocketChannel.swift
[93/144] Compiling NIO SocketOptionProvider.swift
[94/144] Compiling NIO SocketProtocols.swift
[95/144] Compiling NIO System.swift
[96/144] Compiling NIO Thread.swift
[97/144] Compiling NIO ThreadPosix.swift
[98/144] Compiling NIO ThreadWindows.swift
[99/144] Compiling NIO TypeAssistedChannelHandler.swift
[100/144] Compiling NIO UniversalBootstrapSupport.swift
[101/144] Compiling NIO Utilities.swift
[102/144] Compiling NIO DeadChannel.swift
[103/144] Compiling NIO DispathQueue+WithFuture.swift
[104/144] Compiling NIO Embedded.swift
[105/144] Compiling NIO EventLoop.swift
[106/144] Compiling NIO EventLoopFuture.swift
[107/144] Compiling NIO FileDescriptor.swift
[108/144] Compiling NIO FileHandle.swift
[109/144] Compiling NIO FileRegion.swift
[110/144] Compiling NIO GetaddrinfoResolver.swift
[111/144] Compiling NIO HappyEyeballs.swift
[112/144] Compiling NIO Heap.swift
[113/144] Compiling NIO IO.swift
[114/144] Compiling NIO ByteBuffer-views.swift
[115/144] Compiling NIO Channel.swift
[116/144] Compiling NIO ChannelHandler.swift
[117/144] Compiling NIO ChannelHandlers.swift
[118/144] Compiling NIO ChannelInvoker.swift
[119/144] Compiling NIO ChannelOption.swift
[120/144] Compiling NIO ChannelPipeline.swift
[121/144] Compiling NIO CircularBuffer.swift
[122/144] Compiling NIO Codec.swift
[123/144] Compiling NIO ControlMessage.swift
[124/144] Compiling NIO ConvenienceOptionSupport.swift
[125/144] Compiling NIO DatagramVectorReadManager.swift
[126/144] Compiling NIO AddressedEnvelope.swift
[127/144] Compiling NIO BSDSocketAPI.swift
[128/144] Compiling NIO BSDSocketAPIPosix.swift
[129/144] Compiling NIO BSDSocketAPIWindows.swift
[130/144] Compiling NIO BaseSocket.swift
[131/144] Compiling NIO BaseSocketChannel.swift
[132/144] Compiling NIO BaseStreamSocketChannel.swift
[133/144] Compiling NIO Bootstrap.swift
[134/144] Compiling NIO ByteBuffer-aux.swift
[135/144] Compiling NIO ByteBuffer-conversions.swift
[136/144] Compiling NIO ByteBuffer-core.swift
[137/144] Compiling NIO ByteBuffer-int.swift
[138/145] Merging module NIO
[139/153] Wrapping AST for NIO for debugging
[140/157] Compiling NIOHTTP1 HTTPDecoder.swift
[141/157] Compiling NIOHTTP1 NIOHTTPObjectAggregator.swift
[142/157] Compiling NIOHTTP1 NIOHTTPClientUpgradeHandler.swift
[143/157] Compiling NIOHTTP1 HTTPServerPipelineHandler.swift
[144/157] Compiling NIOHTTP1 HTTPServerProtocolErrorHandler.swift
[145/157] Compiling NIOHTTP1 ByteCollectionUtils.swift
[146/158] Compiling NIOHTTP1 HTTPEncoder.swift
[147/158] Compiling NIOHTTP1 HTTPPipelineSetup.swift
[148/158] Merging module NIOFoundationCompat
[149/158] Wrapping AST for NIOFoundationCompat for debugging
[150/158] Compiling NIOHTTP1 HTTPServerUpgradeHandler.swift
[151/158] Compiling NIOHTTP1 HTTPTypes.swift
[154/159] Merging module NIOHTTP1
[155/165] Wrapping AST for NIOHTTP1 for debugging
[156/170] Compiling AWSLambdaRuntimeCore Lambda+String.swift
[157/170] Compiling AWSLambdaRuntimeCore Lambda.swift
[158/170] Compiling AWSLambdaRuntimeCore LambdaHandler.swift
[159/170] Compiling AWSLambdaRuntimeCore LambdaLifecycle.swift
[160/170] Compiling AWSLambdaRuntimeCore LambdaConfiguration.swift
[161/170] Compiling AWSLambdaRuntimeCore LambdaContext.swift
[162/170] Compiling AWSLambdaRuntimeCore Utils.swift
[163/170] Compiling AWSLambdaRuntimeCore LambdaRunner.swift
[164/170] Compiling AWSLambdaRuntimeCore LambdaRuntimeClient.swift
[165/170] Compiling AWSLambdaRuntimeCore HTTPClient.swift
[166/170] Compiling AWSLambdaRuntimeCore Lambda+LocalServer.swift
[167/171] Merging module AWSLambdaRuntimeCore
[168/173] Wrapping AST for AWSLambdaRuntimeCore for debugging
[169/173] Compiling AWSLambdaRuntime Context+Foundation.swift
[170/173] Compiling AWSLambdaRuntime Lambda+Codable.swift
[171/174] Merging module AWSLambdaRuntime
[172/175] Wrapping AST for AWSLambdaRuntime for debugging
[173/175] Compiling IssueProcessorLambda main.swift
[174/176] Merging module IssueProcessorLambda
[175/176] Wrapping AST for IssueProcessorLambda for debugging
[176/176] Linking IssueProcessorLambda
Test Suite 'All tests' started at 2020-12-26 04:10:51.668
Test Suite 'debug.xctest' started at 2020-12-26 04:10:51.698
Test Suite 'BlogTests' started at 2020-12-26 04:10:51.698
Test Case 'BlogTests.testParser' started at 2020-12-26 04:10:51.699
Test Case 'BlogTests.testParser' passed (0.012 seconds)
Test Case 'BlogTests.testPayload' started at 2020-12-26 04:10:51.711
Test Case 'BlogTests.testPayload' passed (0.003 seconds)
Test Case 'BlogTests.testRenderer' started at 2020-12-26 04:10:51.714
Test Case 'BlogTests.testRenderer' passed (0.004 seconds)
Test Case 'BlogTests.testRendererFilename' started at 2020-12-26 04:10:51.718
Test Case 'BlogTests.testRendererFilename' passed (0.002 seconds)
Test Suite 'BlogTests' passed at 2020-12-26 04:10:51.720
	 Executed 4 tests, with 0 failures (0 unexpected) in 0.021 (0.021) seconds
Test Suite 'debug.xctest' passed at 2020-12-26 04:10:51.720
	 Executed 4 tests, with 0 failures (0 unexpected) in 0.021 (0.021) seconds
Test Suite 'All tests' passed at 2020-12-26 04:10:51.720
	 Executed 4 tests, with 0 failures (0 unexpected) in 0.021 (0.021) seconds

Yay! My four tests are passing 😅 Should probably get to write more tests now.


This article was written as an issue on my Blog repository on GitHub (see Issue #12)