1- // Copyright (c) .NET Foundation. All rights reserved.
2- // Licensed under the MIT License. See License.txt in the project root for license information.
1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Diagnostics ;
4+ using System . IO ;
5+ using System . Linq ;
6+ using System . Threading . Tasks ;
37
48namespace LogicAppUnit . Hosting
59{
6- using System ;
7- using System . Collections . Generic ;
8- using System . Diagnostics ;
9- using System . IO ;
10- using System . Linq ;
11- using System . Threading . Tasks ;
12-
1310 /// <summary>
1411 /// The function test host.
1512 /// </summary>
1613 internal class WorkflowTestHost : IDisposable
1714 {
15+ private const string FunctionsExecutableName = "func" ;
16+
1817 /// <summary>
1918 /// Get or sets the output data.
2019 /// </summary>
@@ -127,12 +126,12 @@ protected void StartFunctionRuntime(WorkflowTestInput[] inputs, string localSett
127126 RedirectStandardOutput = true ,
128127 RedirectStandardError = true ,
129128 UseShellExecute = false ,
130- CreateNoWindow = true ,
129+ CreateNoWindow = true
131130 }
132131 } ;
133132
133+ // Hook up an event handler for the Standard Output stream
134134 var processStarted = new TaskCompletionSource < bool > ( ) ;
135-
136135 this . Process . OutputDataReceived += ( sender , args ) =>
137136 {
138137 var outputData = args . Data ;
@@ -153,6 +152,7 @@ protected void StartFunctionRuntime(WorkflowTestInput[] inputs, string localSett
153152 }
154153 } ;
155154
155+ // Hook up an event handler for the Standard Error stream
156156 var errorData = string . Empty ;
157157 this . Process . ErrorDataReceived += ( sender , args ) =>
158158 {
@@ -165,11 +165,12 @@ protected void StartFunctionRuntime(WorkflowTestInput[] inputs, string localSett
165165 }
166166 } ;
167167
168+ // Start the Functions Core Tools process
168169 this . Process . Start ( ) ;
169-
170170 this . Process . BeginOutputReadLine ( ) ;
171171 this . Process . BeginErrorReadLine ( ) ;
172172
173+ // Wait for the Functions runtime to start, or timeout after 2 minutes
173174 var result = Task . WhenAny ( processStarted . Task , Task . Delay ( TimeSpan . FromMinutes ( 2 ) ) ) . Result ;
174175
175176 if ( result != processStarted . Task )
@@ -200,15 +201,15 @@ protected void StartFunctionRuntime(WorkflowTestInput[] inputs, string localSett
200201 /// </summary>
201202 private static void KillFunctionHostProcesses ( )
202203 {
203- Process [ ] processes = Process . GetProcessesByName ( "func" ) ;
204+ Process [ ] processes = Process . GetProcessesByName ( FunctionsExecutableName ) ;
204205 foreach ( var process in processes )
205206 {
206207 process . Kill ( true ) ;
207208 }
208209 }
209210
210211 /// <summary>
211- /// Retrieve the path of the 'func' executable (Azure Function core tools).
212+ /// Retrieve the path of the 'func' executable (Azure Functions Core tools).
212213 /// </summary>
213214 /// <returns>The path to the 'func' executable.</returns>
214215 /// <exception cref="Exception">Thrown when the location of the 'func' executable could not be found.</exception>
@@ -220,24 +221,30 @@ private static string GetEnvPathForFunctionTools()
220221 // Handle the differences between platforms
221222 if ( OperatingSystem . IsWindows ( ) )
222223 {
223- enviromentPath = Environment . GetEnvironmentVariable ( "PATH" , EnvironmentVariableTarget . Process ) ;
224- exeName = "func.exe" ;
224+ // The path to the 'func' executable can be in any of the environment variable scopes, depending on how the Functions Core Tools were installed.
225+ // If a DevOps build pipeline has updated the PATH environment variable for the 'Machine' or 'User' scopes, the 'Process' scope is not automatically updated to reflect the change.
226+ // So merge all three scopes to be sure!
227+ environmentPath = Environment . GetEnvironmentVariable ( "PATH" , EnvironmentVariableTarget . Process ) + Path . PathSeparator +
228+ Environment . GetEnvironmentVariable ( "PATH" , EnvironmentVariableTarget . User ) + Path . PathSeparator +
229+ Environment . GetEnvironmentVariable ( "PATH" , EnvironmentVariableTarget . Machine ) ;
230+ exeName = $ "{ FunctionsExecutableName } .exe";
225231 }
226232 else
227233 {
228234 environmentPath = Environment . GetEnvironmentVariable ( "PATH" ) ;
229- exeName = "func" ;
235+ exeName = FunctionsExecutableName ;
230236 }
231237
232- string exePath = environmentPath . Split ( Path . PathSeparator ) . Select ( x => Path . Combine ( x , exeName ) ) . Where ( x => File . Exists ( x ) ) . FirstOrDefault ( ) ;
233- if ( ! string . IsNullOrWhiteSpace ( exePath ) )
238+ var exePaths = environmentPath . Split ( Path . PathSeparator ) . Distinct ( ) . Select ( x => Path . Combine ( x , exeName ) ) ;
239+ string exePathMatch = exePaths . Where ( x => File . Exists ( x ) ) . FirstOrDefault ( ) ;
240+ if ( ! string . IsNullOrWhiteSpace ( exePathMatch ) )
234241 {
235- Console . WriteLine ( $ "Path for Azure Function Core tools: { exePath } ") ;
236- return exePath ;
242+ Console . WriteLine ( $ "Path for Azure Function Core tools: { exePathMatch } ") ;
243+ return exePathMatch ;
237244 }
238245 else
239246 {
240- throw new Exception ( "The enviroment variable PATH does not include the path for the 'func ' executable." ) ;
247+ throw new TestException ( $ "The enviroment variable PATH does not include the path for the '{ FunctionsExecutableName } ' executable. Searched: { string . Join ( Path . PathSeparator , exePaths ) } ") ;
241248 }
242249 }
243250
0 commit comments