@@ -21,6 +21,7 @@ public class DockerFixture : IDisposable
2121 private ILogger [ ] loggers ;
2222 private int startupTimeoutSecs ;
2323 private readonly IMessageSink output ;
24+ private ushort [ ] requiredPorts ;
2425
2526 public DockerFixture ( IMessageSink output )
2627 {
@@ -31,7 +32,7 @@ public DockerFixture(IMessageSink output)
3132 /// Initialize docker compose services from file(s) but only once.
3233 /// If you call this multiple times on the same DockerFixture then it will be ignored.
3334 /// </summary>
34- /// <param name="setupOptions">Options that control how docker- compose is executed.</param>
35+ /// <param name="setupOptions">Options that control how docker compose is executed.</param>
3536 public void InitOnce ( Func < IDockerFixtureOptions > setupOptions )
3637 {
3738 InitOnce ( setupOptions , null ) ;
@@ -41,7 +42,7 @@ public void InitOnce(Func<IDockerFixtureOptions> setupOptions)
4142 /// Initialize docker compose services from file(s) but only once.
4243 /// If you call this multiple times on the same DockerFixture then it will be ignored.
4344 /// </summary>
44- /// <param name="setupOptions">Options that control how docker- compose is executed.</param>
45+ /// <param name="setupOptions">Options that control how docker compose is executed.</param>
4546 /// <param name="dockerCompose"></param>
4647 public void InitOnce ( Func < IDockerFixtureOptions > setupOptions , IDockerCompose dockerCompose )
4748 {
@@ -56,7 +57,7 @@ public void InitOnce(Func<IDockerFixtureOptions> setupOptions, IDockerCompose do
5657 /// <summary>
5758 /// Initialize docker compose services from file(s).
5859 /// </summary>
59- /// <param name="setupOptions">Options that control how docker- compose is executed</param>
60+ /// <param name="setupOptions">Options that control how docker compose is executed</param>
6061 public void Init ( Func < IDockerFixtureOptions > setupOptions )
6162 {
6263 Init ( setupOptions , null ) ;
@@ -65,18 +66,25 @@ public void Init(Func<IDockerFixtureOptions> setupOptions)
6566 /// <summary>
6667 /// Initialize docker compose services from file(s).
6768 /// </summary>
68- /// <param name="setupOptions">Options that control how docker- compose is executed</param>
69+ /// <param name="setupOptions">Options that control how docker compose is executed</param>
6970 /// <param name="compose"></param>
7071 public void Init ( Func < IDockerFixtureOptions > setupOptions , IDockerCompose compose )
7172 {
7273 var options = setupOptions ( ) ;
7374 options . Validate ( ) ;
74- string logFile = options . DebugLog
75+ var logFile = options . DebugLog
7576 ? Path . Combine ( Path . GetTempPath ( ) , $ "docker-compose-{ DateTime . Now . Ticks } .log")
7677 : null ;
7778
78- this . Init ( options . DockerComposeFiles , options . DockerComposeUpArgs , options . DockerComposeDownArgs ,
79- options . StartupTimeoutSecs , options . CustomUpTest , compose , this . GetLoggers ( logFile ) . ToArray ( ) ) ;
79+ this . Init (
80+ options . DockerComposeFiles ,
81+ options . DockerComposeUpArgs ,
82+ options . DockerComposeDownArgs ,
83+ options . StartupTimeoutSecs ,
84+ options . CustomUpTest ,
85+ compose ,
86+ this . GetLoggers ( logFile ) . ToArray ( ) ,
87+ options . RequiredPorts ) ;
8088 }
8189
8290 private IEnumerable < ILogger > GetLoggers ( string file )
@@ -97,22 +105,24 @@ private IEnumerable<ILogger> GetLoggers(string file)
97105 /// Initialize docker compose services from file(s).
98106 /// </summary>
99107 /// <param name="dockerComposeFiles">Array of docker compose files</param>
100- /// <param name="dockerComposeUpArgs">Arguments to append after 'docker- compose -f file.yml up'</param>
101- /// <param name="dockerComposeDownArgs">Arguments to append after 'docker- compose -f file.yml down'</param>
108+ /// <param name="dockerComposeUpArgs">Arguments to append after 'docker compose -f file.yml up'</param>
109+ /// <param name="dockerComposeDownArgs">Arguments to append after 'docker compose -f file.yml down'</param>
102110 /// <param name="startupTimeoutSecs">How long to wait for the application to start before giving up</param>
103- /// <param name="customUpTest">Checks whether the docker- compose services have come up correctly based upon the output of docker- compose</param>
111+ /// <param name="customUpTest">Checks whether the docker compose services have come up correctly based upon the output of docker compose</param>
104112 /// <param name="dockerCompose"></param>
105113 /// <param name="logger"></param>
114+ /// <param name="requiredPorts">Checks that these ports are available on the host network (not in use by other processes)</param>
106115 public void Init ( string [ ] dockerComposeFiles , string dockerComposeUpArgs , string dockerComposeDownArgs ,
107116 int startupTimeoutSecs , Func < string [ ] , bool > customUpTest = null ,
108- IDockerCompose dockerCompose = null , ILogger [ ] logger = null )
117+ IDockerCompose dockerCompose = null , ILogger [ ] logger = null , ushort [ ] requiredPorts = null )
109118 {
110119 this . loggers = logger ?? GetLoggers ( null ) . ToArray ( ) ;
111120
112121 var dockerComposeFilePaths = dockerComposeFiles . Select ( this . GetComposeFilePath ) ;
113122 this . dockerCompose = dockerCompose ?? new DockerCompose ( this . loggers ) ;
114123 this . customUpTest = customUpTest ;
115124 this . startupTimeoutSecs = startupTimeoutSecs ;
125+ this . requiredPorts = requiredPorts ;
116126
117127 this . dockerCompose . Init (
118128 string . Join ( " " ,
@@ -129,7 +139,7 @@ private string GetComposeFilePath(string file)
129139 return file ;
130140 }
131141
132- DirectoryInfo curDir = new DirectoryInfo ( AppDomain . CurrentDomain . BaseDirectory ) ;
142+ var curDir = new DirectoryInfo ( AppDomain . CurrentDomain . BaseDirectory ) ;
133143 if ( File . Exists ( Path . Combine ( curDir . FullName , file ) ) )
134144 {
135145 return Path . Combine ( curDir . FullName , file ) ;
@@ -139,7 +149,7 @@ private string GetComposeFilePath(string file)
139149 {
140150 while ( curDir != null )
141151 {
142- string curFile = Path . Combine ( curDir . FullName , file ) ;
152+ var curFile = Path . Combine ( curDir . FullName , file ) ;
143153 if ( File . Exists ( curFile ) )
144154 {
145155 return curFile ;
@@ -169,7 +179,7 @@ public static async Task Kill(string applicationName, bool killEverything = fals
169179 /// <returns></returns>
170180 public static async Task Kill ( Regex filterRx , bool killEverything = false )
171181 {
172- Process ps = Process . Start ( new ProcessStartInfo ( "docker" , "ps" )
182+ var ps = Process . Start ( new ProcessStartInfo ( "docker" , "ps" )
173183 {
174184 UseShellExecute = false ,
175185 RedirectStandardOutput = true
@@ -187,7 +197,6 @@ public static async Task Kill(Regex filterRx, bool killEverything = false)
187197 {
188198 Process . Start ( "docker" , $ "kill { id } ") . WaitForExit ( ) ;
189199 }
190-
191200 }
192201
193202 public virtual void Dispose ( )
@@ -203,14 +212,21 @@ private void Start()
203212 this . Stop ( ) ;
204213 }
205214
215+ if ( requiredPorts != null && requiredPorts . Length > 0 )
216+ {
217+ this . loggers . Log ( "---- checking for port availability ----" ) ;
218+ this . CheckForRequiredPorts ( ) ;
219+ this . loggers . Log ( "---- all required host ports are available ----" ) ;
220+ }
221+
206222 this . loggers . Log ( "---- starting docker services ----" ) ;
207223 var upTask = this . dockerCompose . Up ( ) ;
208224
209- for ( int i = 0 ; i < this . startupTimeoutSecs ; i ++ )
225+ for ( var i = 0 ; i < this . startupTimeoutSecs ; i ++ )
210226 {
211227 if ( upTask . IsCompleted )
212228 {
213- this . loggers . Log ( "docker- compose exited prematurely" ) ;
229+ this . loggers . Log ( "docker compose exited prematurely" ) ;
214230 break ;
215231 }
216232 this . loggers . Log ( $ "---- checking docker services ({ i + 1 } /{ this . startupTimeoutSecs } ) ----") ;
@@ -235,7 +251,16 @@ private void Start()
235251 }
236252 throw new DockerComposeException ( this . loggers . GetLoggedLines ( ) ) ;
237253 }
238-
254+
255+ private void CheckForRequiredPorts ( )
256+ {
257+ var usedPorts = Ports . DetermineUtilizedPorts ( requiredPorts ) ;
258+ if ( usedPorts . Any ( ) )
259+ {
260+ throw new PortsUnavailableException ( this . loggers . GetLoggedLines ( ) , usedPorts ) ;
261+ }
262+ }
263+
239264 private ( bool hasContainers , bool containersAreUp ) CheckIfRunning ( )
240265 {
241266 var lines = dockerCompose . PsWithJsonFormat ( )
0 commit comments