diff --git a/src/FunctionLoader.cs b/src/FunctionLoader.cs
index 3d666cce..cb77b2fe 100644
--- a/src/FunctionLoader.cs
+++ b/src/FunctionLoader.cs
@@ -21,7 +21,7 @@ internal class FunctionLoader
internal static string FunctionAppRootPath { get; private set; }
internal static string FunctionAppProfilePath { get; private set; }
- internal static string FunctionAppModulesPath { get; private set; }
+ internal static string FunctionModulePath { get; private set; }
///
/// Query for function metadata can happen in parallel.
@@ -51,9 +51,14 @@ internal void LoadFunction(FunctionLoadRequest request)
///
internal static void SetupWellKnownPaths(FunctionLoadRequest request)
{
+ // Resolve the FunctionApp root path
FunctionAppRootPath = Path.GetFullPath(Path.Join(request.Metadata.Directory, ".."));
- FunctionAppModulesPath = Path.Join(FunctionAppRootPath, "Modules");
+ // Resolve module paths
+ var appLevelModulesPath = Path.Join(FunctionAppRootPath, "Modules");
+ var workerLevelModulesPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Modules");
+ FunctionModulePath = $"{appLevelModulesPath}{Path.PathSeparator}{workerLevelModulesPath}";
+ // Resolve the FunctionApp profile path
var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
var profiles = Directory.EnumerateFiles(FunctionAppRootPath, "profile.ps1", options);
FunctionAppProfilePath = profiles.FirstOrDefault();
diff --git a/src/PowerShell/PowerShellManager.cs b/src/PowerShell/PowerShellManager.cs
index a61ebda3..1a929a78 100644
--- a/src/PowerShell/PowerShellManager.cs
+++ b/src/PowerShell/PowerShellManager.cs
@@ -23,9 +23,33 @@ internal class PowerShellManager
private readonly ILogger _logger;
private readonly PowerShell _pwsh;
+ ///
+ /// Gets the Runspace InstanceId.
+ ///
+ internal Guid InstanceId => _pwsh.Runspace.InstanceId;
+
+ static PowerShellManager()
+ {
+ // Set the type accelerators for 'HttpResponseContext' and 'HttpResponseContext'.
+ // We probably will expose more public types from the worker in future for the interop between worker and the 'PowerShellWorker' module.
+ // But it's most likely only 'HttpResponseContext' and 'HttpResponseContext' are supposed to be used directly by users, so we only add
+ // type accelerators for these two explicitly.
+ var accelerator = typeof(PSObject).Assembly.GetType("System.Management.Automation.TypeAccelerators");
+ var addMethod = accelerator.GetMethod("Add", new Type[] { typeof(string), typeof(Type) });
+ addMethod.Invoke(null, new object[] { "HttpResponseContext", typeof(HttpResponseContext) });
+ addMethod.Invoke(null, new object[] { "HttpRequestContext", typeof(HttpRequestContext) });
+ }
+
internal PowerShellManager(ILogger logger)
{
+ if (FunctionLoader.FunctionAppRootPath == null)
+ {
+ throw new InvalidOperationException($"The FunctionApp root hasn't been resolved yet!");
+ }
+
var initialSessionState = InitialSessionState.CreateDefault();
+ initialSessionState.EnvironmentVariables.Add(
+ new SessionStateVariableEntry("PSModulePath", FunctionLoader.FunctionModulePath, null));
// Setting the execution policy on macOS and Linux throws an exception so only update it on Windows
if(Platform.IsWindows)
@@ -34,8 +58,9 @@ internal PowerShellManager(ILogger logger)
// Windows client versions. This is needed if a user is testing their function locally with the func CLI
initialSessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
}
- _pwsh = PowerShell.Create(initialSessionState);
+
_logger = logger;
+ _pwsh = PowerShell.Create(initialSessionState);
// Setup Stream event listeners
var streamHandler = new StreamHandler(logger);
@@ -45,34 +70,17 @@ internal PowerShellManager(ILogger logger)
_pwsh.Streams.Progress.DataAdding += streamHandler.ProgressDataAdding;
_pwsh.Streams.Verbose.DataAdding += streamHandler.VerboseDataAdding;
_pwsh.Streams.Warning.DataAdding += streamHandler.WarningDataAdding;
- }
- ///
- /// This method performs the one-time initialization at the worker process level.
- ///
- internal void PerformWorkerLevelInitialization()
- {
- // Set the type accelerators for 'HttpResponseContext' and 'HttpResponseContext'.
- // We probably will expose more public types from the worker in future for the interop between worker and the 'PowerShellWorker' module.
- // But it's most likely only 'HttpResponseContext' and 'HttpResponseContext' are supposed to be used directly by users, so we only add
- // type accelerators for these two explicitly.
- var accelerator = typeof(PSObject).Assembly.GetType("System.Management.Automation.TypeAccelerators");
- var addMethod = accelerator.GetMethod("Add", new Type[] { typeof(string), typeof(Type) });
- addMethod.Invoke(null, new object[] { "HttpResponseContext", typeof(HttpResponseContext) });
- addMethod.Invoke(null, new object[] { "HttpRequestContext", typeof(HttpRequestContext) });
-
- // Set the PSModulePath
- var workerModulesPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Modules");
- Environment.SetEnvironmentVariable("PSModulePath", $"{FunctionLoader.FunctionAppModulesPath}{Path.PathSeparator}{workerModulesPath}");
+ // Initialize the Runspace
+ InvokeProfile(FunctionLoader.FunctionAppProfilePath);
}
///
- /// This method performs initialization that has to be done for each Runspace, e.g. running the Function App's profile.ps1.
+ /// This method invokes the FunctionApp's profile.ps1.
///
- internal void PerformRunspaceLevelInitialization()
+ internal void InvokeProfile(string profilePath)
{
Exception exception = null;
- string profilePath = FunctionLoader.FunctionAppProfilePath;
if (profilePath == null)
{
_logger.Log(LogLevel.Trace, $"No 'profile.ps1' is found at the FunctionApp root folder: {FunctionLoader.FunctionAppRootPath}");
@@ -195,25 +203,6 @@ internal string ConvertToJson(object fromObj)
.InvokeAndClearCommands()[0];
}
- ///
- /// Helper method to set the output binding metadata for the function that is about to run.
- ///
- internal void RegisterFunctionMetadata(AzFunctionInfo functionInfo)
- {
- var outputBindings = functionInfo.OutputBindings;
- FunctionMetadata.OutputBindingCache.AddOrUpdate(_pwsh.Runspace.InstanceId,
- outputBindings,
- (key, value) => outputBindings);
- }
-
- ///
- /// Helper method to clear the output binding metadata for the function that has done running.
- ///
- internal void UnregisterFunctionMetadata()
- {
- FunctionMetadata.OutputBindingCache.TryRemove(_pwsh.Runspace.InstanceId, out _);
- }
-
private void ResetRunspace(string moduleName)
{
// Reset the runspace to the Initial Session State
diff --git a/src/PowerShell/PowerShellManagerPool.cs b/src/PowerShell/PowerShellManagerPool.cs
new file mode 100644
index 00000000..06b0c583
--- /dev/null
+++ b/src/PowerShell/PowerShellManagerPool.cs
@@ -0,0 +1,63 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using Microsoft.Azure.Functions.PowerShellWorker.Utility;
+
+namespace Microsoft.Azure.Functions.PowerShellWorker.PowerShell
+{
+ using System.Management.Automation;
+
+ ///
+ /// The PowerShellManager pool for the in-proc concurrency support.
+ ///
+ internal class PowerShellManagerPool
+ {
+ private readonly ILogger _logger;
+ // Today we don't really support the in-proc concurrency. We just hold an instance of PowerShellManager in this field.
+ private PowerShellManager _psManager;
+
+ ///
+ /// Constructor of the pool.
+ ///
+ internal PowerShellManagerPool(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// Initialize the pool and populate it with PowerShellManager instances.
+ /// When it's time to really implement this pool, we probably should instantiate PowerShellManager instances in a lazy way.
+ /// Maybe start from size 1 and increase the number of workers as needed.
+ ///
+ internal void Initialize()
+ {
+ _psManager = new PowerShellManager(_logger);
+ }
+
+ ///
+ /// Checkout an idle PowerShellManager instance.
+ /// When it's time to really implement this pool, this method is supposed to block when there is no idle instance available.
+ ///
+ internal PowerShellManager CheckoutIdleWorker(AzFunctionInfo functionInfo)
+ {
+ // Register the function with the Runspace before returning the idle PowerShellManager.
+ FunctionMetadata.RegisterFunctionMetadata(_psManager.InstanceId, functionInfo);
+ return _psManager;
+ }
+
+ ///
+ /// Return a used PowerShellManager instance to the pool.
+ ///
+ internal void ReclaimUsedWorker(PowerShellManager psManager)
+ {
+ if (psManager != null)
+ {
+ // Unregister the Runspace before reclaiming the used PowerShellManager.
+ FunctionMetadata.UnregisterFunctionMetadata(psManager.InstanceId);
+ }
+ }
+ }
+}
diff --git a/src/Public/FunctionMetadata.cs b/src/Public/FunctionMetadata.cs
index 442f5f61..0fcf942a 100644
--- a/src/Public/FunctionMetadata.cs
+++ b/src/Public/FunctionMetadata.cs
@@ -26,5 +26,22 @@ public static ReadOnlyDictionary GetOutputBindingIn
OutputBindingCache.TryGetValue(runspaceInstanceId, out outputBindings);
return outputBindings;
}
+
+ ///
+ /// Helper method to set the output binding metadata for the function that is about to run.
+ ///
+ internal static void RegisterFunctionMetadata(Guid instanceId, AzFunctionInfo functionInfo)
+ {
+ var outputBindings = functionInfo.OutputBindings;
+ OutputBindingCache.AddOrUpdate(instanceId, outputBindings, (key, value) => outputBindings);
+ }
+
+ ///
+ /// Helper method to clear the output binding metadata for the function that has done running.
+ ///
+ internal static void UnregisterFunctionMetadata(Guid instanceId)
+ {
+ OutputBindingCache.TryRemove(instanceId, out _);
+ }
}
}
diff --git a/src/RequestProcessor.cs b/src/RequestProcessor.cs
index 4d38309c..6f005d2e 100644
--- a/src/RequestProcessor.cs
+++ b/src/RequestProcessor.cs
@@ -20,7 +20,7 @@ internal class RequestProcessor
private readonly FunctionLoader _functionLoader;
private readonly RpcLogger _logger;
private readonly MessagingStream _msgStream;
- private readonly PowerShellManager _powerShellManager;
+ private readonly PowerShellManagerPool _powershellPool;
// Indicate whether the FunctionApp has been initialized.
private bool _isFunctionAppInitialized;
@@ -29,7 +29,7 @@ internal RequestProcessor(MessagingStream msgStream)
{
_msgStream = msgStream;
_logger = new RpcLogger(msgStream);
- _powerShellManager = new PowerShellManager(_logger);
+ _powershellPool = new PowerShellManagerPool(_logger);
_functionLoader = new FunctionLoader();
}
@@ -98,9 +98,7 @@ internal StreamingMessage ProcessFunctionLoadRequest(StreamingMessage request)
if (!_isFunctionAppInitialized)
{
FunctionLoader.SetupWellKnownPaths(functionLoadRequest);
- _powerShellManager.PerformWorkerLevelInitialization();
- _powerShellManager.PerformRunspaceLevelInitialization();
-
+ _powershellPool.Initialize();
_isFunctionAppInitialized = true;
}
@@ -122,6 +120,7 @@ internal StreamingMessage ProcessFunctionLoadRequest(StreamingMessage request)
///
internal StreamingMessage ProcessInvocationRequest(StreamingMessage request)
{
+ PowerShellManager psManager = null;
InvocationRequest invocationRequest = request.InvocationRequest;
StreamingMessage response = NewStreamingMessageTemplate(
@@ -130,18 +129,18 @@ internal StreamingMessage ProcessInvocationRequest(StreamingMessage request)
out StatusResult status);
response.InvocationResponse.InvocationId = invocationRequest.InvocationId;
- // Invoke powershell logic and return hashtable of out binding data
try
{
// Load information about the function
var functionInfo = _functionLoader.GetFunctionInfo(invocationRequest.FunctionId);
- _powerShellManager.RegisterFunctionMetadata(functionInfo);
+ psManager = _powershellPool.CheckoutIdleWorker(functionInfo);
+ // Invoke the function and return a hashtable of out binding data
Hashtable results = functionInfo.Type == AzFunctionType.OrchestrationFunction
- ? InvokeOrchestrationFunction(functionInfo, invocationRequest)
- : InvokeSingleActivityFunction(functionInfo, invocationRequest);
+ ? InvokeOrchestrationFunction(psManager, functionInfo, invocationRequest)
+ : InvokeSingleActivityFunction(psManager, functionInfo, invocationRequest);
- BindOutputFromResult(response.InvocationResponse, functionInfo, results);
+ BindOutputFromResult(psManager, response.InvocationResponse, functionInfo, results);
}
catch (Exception e)
{
@@ -150,7 +149,7 @@ internal StreamingMessage ProcessInvocationRequest(StreamingMessage request)
}
finally
{
- _powerShellManager.UnregisterFunctionMetadata();
+ _powershellPool.ReclaimUsedWorker(psManager);
}
return response;
@@ -188,7 +187,7 @@ private StreamingMessage NewStreamingMessageTemplate(string requestId, Streaming
///
/// Invoke an orchestration function.
///
- private Hashtable InvokeOrchestrationFunction(AzFunctionInfo functionInfo, InvocationRequest invocationRequest)
+ private Hashtable InvokeOrchestrationFunction(PowerShellManager psManager, AzFunctionInfo functionInfo, InvocationRequest invocationRequest)
{
throw new NotImplementedException("Durable function is not yet supported for PowerShell");
}
@@ -196,7 +195,7 @@ private Hashtable InvokeOrchestrationFunction(AzFunctionInfo functionInfo, Invoc
///
/// Invoke a regular function or an activity function.
///
- private Hashtable InvokeSingleActivityFunction(AzFunctionInfo functionInfo, InvocationRequest invocationRequest)
+ private Hashtable InvokeSingleActivityFunction(PowerShellManager psManager, AzFunctionInfo functionInfo, InvocationRequest invocationRequest)
{
// Bundle all TriggerMetadata into Hashtable to send down to PowerShell
var triggerMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase);
@@ -210,16 +209,13 @@ private Hashtable InvokeSingleActivityFunction(AzFunctionInfo functionInfo, Invo
}
}
- return _powerShellManager.InvokeFunction(
- functionInfo,
- triggerMetadata,
- invocationRequest.InputData);
+ return psManager.InvokeFunction(functionInfo, triggerMetadata, invocationRequest.InputData);
}
///
/// Set the 'ReturnValue' and 'OutputData' based on the invocation results appropriately.
///
- private void BindOutputFromResult(InvocationResponse response, AzFunctionInfo functionInfo, Hashtable results)
+ private void BindOutputFromResult(PowerShellManager psManager, InvocationResponse response, AzFunctionInfo functionInfo, Hashtable results)
{
switch (functionInfo.Type)
{
@@ -231,14 +227,14 @@ private void BindOutputFromResult(InvocationResponse response, AzFunctionInfo fu
string outBindingName = binding.Key;
if(string.Equals(outBindingName, AzFunctionInfo.DollarReturn, StringComparison.OrdinalIgnoreCase))
{
- response.ReturnValue = results[outBindingName].ToTypedData(_powerShellManager);
+ response.ReturnValue = results[outBindingName].ToTypedData(psManager);
continue;
}
ParameterBinding paramBinding = new ParameterBinding()
{
Name = outBindingName,
- Data = results[outBindingName].ToTypedData(_powerShellManager)
+ Data = results[outBindingName].ToTypedData(psManager)
};
response.OutputData.Add(paramBinding);
@@ -247,7 +243,7 @@ private void BindOutputFromResult(InvocationResponse response, AzFunctionInfo fu
case AzFunctionType.OrchestrationFunction:
case AzFunctionType.ActivityFunction:
- response.ReturnValue = results[AzFunctionInfo.DollarReturn].ToTypedData(_powerShellManager);
+ response.ReturnValue = results[AzFunctionInfo.DollarReturn].ToTypedData(psManager);
break;
default:
diff --git a/test/Unit/PowerShell/PowerShellManagerTests.cs b/test/Unit/PowerShell/PowerShellManagerTests.cs
index 89995a42..f74bcf2e 100644
--- a/test/Unit/PowerShell/PowerShellManagerTests.cs
+++ b/test/Unit/PowerShell/PowerShellManagerTests.cs
@@ -14,44 +14,77 @@
namespace Microsoft.Azure.Functions.PowerShellWorker.Test
{
- public class PowerShellManagerTests
+ internal class TestUtils
{
- private const string TestInputBindingName = "req";
- private const string TestOutputBindingName = "res";
- private const string TestStringData = "Foo";
+ internal const string TestInputBindingName = "req";
+ internal const string TestOutputBindingName = "res";
- private readonly string _functionDirectory;
- private readonly ConsoleLogger _testLogger;
- private readonly PowerShellManager _testManager;
- private readonly List _testInputData;
- private readonly RpcFunctionMetadata _rpcFunctionMetadata;
- private readonly FunctionLoadRequest _functionLoadRequest;
+ internal static readonly string FunctionDirectory;
+ internal static readonly RpcFunctionMetadata RpcFunctionMetadata;
+ internal static readonly FunctionLoadRequest FunctionLoadRequest;
- public PowerShellManagerTests()
+ static TestUtils()
{
- _functionDirectory = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "TestScripts", "PowerShell");
- _rpcFunctionMetadata = new RpcFunctionMetadata()
+ FunctionDirectory = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "TestScripts", "PowerShell");
+ RpcFunctionMetadata = new RpcFunctionMetadata()
{
Name = "TestFuncApp",
- Directory = _functionDirectory,
+ Directory = FunctionDirectory,
Bindings =
{
{ TestInputBindingName , new BindingInfo { Direction = BindingInfo.Types.Direction.In, Type = "httpTrigger" } },
{ TestOutputBindingName, new BindingInfo { Direction = BindingInfo.Types.Direction.Out, Type = "http" } }
}
};
- _functionLoadRequest = new FunctionLoadRequest {FunctionId = "FunctionId", Metadata = _rpcFunctionMetadata};
- FunctionLoader.SetupWellKnownPaths(_functionLoadRequest);
+ FunctionLoadRequest = new FunctionLoadRequest {FunctionId = "FunctionId", Metadata = RpcFunctionMetadata};
+ FunctionLoader.SetupWellKnownPaths(FunctionLoadRequest);
+ }
+
+ // Have a single place to get a PowerShellManager for testing.
+ // This is to guarantee that the well known paths are setup before calling the constructor of PowerShellManager.
+ internal static PowerShellManager NewTestPowerShellManager(ConsoleLogger logger)
+ {
+ return new PowerShellManager(logger);
+ }
+
+ internal static AzFunctionInfo NewAzFunctionInfo(string scriptFile, string entryPoint)
+ {
+ RpcFunctionMetadata.ScriptFile = scriptFile;
+ RpcFunctionMetadata.EntryPoint = entryPoint;
+ RpcFunctionMetadata.Directory = Path.GetDirectoryName(scriptFile);
+ return new AzFunctionInfo(RpcFunctionMetadata);
+ }
+
+ // Helper method to wait for debugger to attach and set a breakpoint.
+ internal static void Break()
+ {
+ while (!System.Diagnostics.Debugger.IsAttached)
+ {
+ System.Threading.Thread.Sleep(200);
+ }
+ System.Diagnostics.Debugger.Break();
+ }
+ }
+
+ public class PowerShellManagerTests
+ {
+ private const string TestStringData = "Foo";
+
+ private readonly ConsoleLogger _testLogger;
+ private readonly PowerShellManager _testManager;
+ private readonly List _testInputData;
+
+ public PowerShellManagerTests()
+ {
_testLogger = new ConsoleLogger();
- _testManager = new PowerShellManager(_testLogger);
- _testManager.PerformWorkerLevelInitialization();
+ _testManager = TestUtils.NewTestPowerShellManager(_testLogger);
_testInputData = new List
{
new ParameterBinding
{
- Name = TestInputBindingName,
+ Name = TestUtils.TestInputBindingName,
Data = new TypedData
{
String = TestStringData
@@ -60,65 +93,58 @@ public PowerShellManagerTests()
};
}
- private AzFunctionInfo GetAzFunctionInfo(string scriptFile, string entryPoint)
- {
- _rpcFunctionMetadata.ScriptFile = scriptFile;
- _rpcFunctionMetadata.EntryPoint = entryPoint;
- return new AzFunctionInfo(_rpcFunctionMetadata);
- }
-
[Fact]
public void InvokeBasicFunctionWorks()
{
- string path = Path.Join(_functionDirectory, "testBasicFunction.ps1");
+ string path = Path.Join(TestUtils.FunctionDirectory, "testBasicFunction.ps1");
- var functionInfo = GetAzFunctionInfo(path, string.Empty);
+ var functionInfo = TestUtils.NewAzFunctionInfo(path, string.Empty);
Hashtable result = _testManager.InvokeFunction(functionInfo, null, _testInputData);
- Assert.Equal(TestStringData, result[TestOutputBindingName]);
+ Assert.Equal(TestStringData, result[TestUtils.TestOutputBindingName]);
}
[Fact]
public void InvokeBasicFunctionWithTriggerMetadataWorks()
{
- string path = Path.Join(_functionDirectory, "testBasicFunctionWithTriggerMetadata.ps1");
+ string path = Path.Join(TestUtils.FunctionDirectory, "testBasicFunctionWithTriggerMetadata.ps1");
Hashtable triggerMetadata = new Hashtable(StringComparer.OrdinalIgnoreCase)
{
- { TestInputBindingName, TestStringData }
+ { TestUtils.TestInputBindingName, TestStringData }
};
- var functionInfo = GetAzFunctionInfo(path, string.Empty);
+ var functionInfo = TestUtils.NewAzFunctionInfo(path, string.Empty);
Hashtable result = _testManager.InvokeFunction(functionInfo, triggerMetadata, _testInputData);
- Assert.Equal(TestStringData, result[TestOutputBindingName]);
+ Assert.Equal(TestStringData, result[TestUtils.TestOutputBindingName]);
}
[Fact]
public void InvokeFunctionWithEntryPointWorks()
{
- string path = Path.Join(_functionDirectory, "testFunctionWithEntryPoint.psm1");
- var functionInfo = GetAzFunctionInfo(path, "Run");
+ string path = Path.Join(TestUtils.FunctionDirectory, "testFunctionWithEntryPoint.psm1");
+ var functionInfo = TestUtils.NewAzFunctionInfo(path, "Run");
Hashtable result = _testManager.InvokeFunction(functionInfo, null, _testInputData);
- Assert.Equal(TestStringData, result[TestOutputBindingName]);
+ Assert.Equal(TestStringData, result[TestUtils.TestOutputBindingName]);
}
[Fact]
public void FunctionShouldCleanupVariableTable()
{
- string path = Path.Join(_functionDirectory, "testFunctionCleanup.ps1");
- var functionInfo = GetAzFunctionInfo(path, string.Empty);
+ string path = Path.Join(TestUtils.FunctionDirectory, "testFunctionCleanup.ps1");
+ var functionInfo = TestUtils.NewAzFunctionInfo(path, string.Empty);
Hashtable result1 = _testManager.InvokeFunction(functionInfo, null, _testInputData);
- Assert.Equal("is not set", result1[TestOutputBindingName]);
+ Assert.Equal("is not set", result1[TestUtils.TestOutputBindingName]);
- // the value shoould not change if the variable table is properly cleaned up.
+ // the value should not change if the variable table is properly cleaned up.
Hashtable result2 = _testManager.InvokeFunction(functionInfo, null, _testInputData);
- Assert.Equal("is not set", result2[TestOutputBindingName]);
+ Assert.Equal("is not set", result2[TestUtils.TestOutputBindingName]);
}
[Fact]
- public void ModulePathShouldBeSetByWorkerLevelInitialization()
+ public void ModulePathShouldBeSetCorrectly()
{
string workerModulePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Modules");
string funcAppModulePath = Path.Join(FunctionLoader.FunctionAppRootPath, "Modules");
@@ -129,13 +155,13 @@ public void ModulePathShouldBeSetByWorkerLevelInitialization()
[Fact]
public void RegisterAndUnregisterFunctionMetadataShouldWork()
{
- string path = Path.Join(_functionDirectory, "testBasicFunction.ps1");
- var functionInfo = GetAzFunctionInfo(path, string.Empty);
+ string path = Path.Join(TestUtils.FunctionDirectory, "testBasicFunction.ps1");
+ var functionInfo = TestUtils.NewAzFunctionInfo(path, string.Empty);
Assert.Empty(FunctionMetadata.OutputBindingCache);
- _testManager.RegisterFunctionMetadata(functionInfo);
+ FunctionMetadata.RegisterFunctionMetadata(_testManager.InstanceId, functionInfo);
Assert.Single(FunctionMetadata.OutputBindingCache);
- _testManager.UnregisterFunctionMetadata();
+ FunctionMetadata.UnregisterFunctionMetadata(_testManager.InstanceId);
Assert.Empty(FunctionMetadata.OutputBindingCache);
}
@@ -144,21 +170,11 @@ public void ProfileShouldWork()
{
//initialize fresh log
_testLogger.FullLog.Clear();
- var funcLoadReq = _functionLoadRequest.Clone();
- funcLoadReq.Metadata.Directory = Path.Join(_functionDirectory, "ProfileBasic", "Func1");
+ var profilePath = Path.Join(TestUtils.FunctionDirectory, "ProfileBasic", "profile.ps1");
+ _testManager.InvokeProfile(profilePath);
- try
- {
- FunctionLoader.SetupWellKnownPaths(funcLoadReq);
- _testManager.PerformRunspaceLevelInitialization();
-
- Assert.Single(_testLogger.FullLog);
- Assert.Equal("Information: INFORMATION: Hello PROFILE", _testLogger.FullLog[0]);
- }
- finally
- {
- FunctionLoader.SetupWellKnownPaths(_functionLoadRequest);
- }
+ Assert.Single(_testLogger.FullLog);
+ Assert.Equal("Information: INFORMATION: Hello PROFILE", _testLogger.FullLog[0]);
}
[Fact]
@@ -166,21 +182,10 @@ public void ProfileDoesNotExist()
{
//initialize fresh log
_testLogger.FullLog.Clear();
- var funcLoadReq = _functionLoadRequest.Clone();
- funcLoadReq.Metadata.Directory = AppDomain.CurrentDomain.BaseDirectory;
-
- try
- {
- FunctionLoader.SetupWellKnownPaths(funcLoadReq);
- _testManager.PerformRunspaceLevelInitialization();
+ _testManager.InvokeProfile(null);
- Assert.Single(_testLogger.FullLog);
- Assert.Matches("Trace: No 'profile.ps1' is found at the FunctionApp root folder: ", _testLogger.FullLog[0]);
- }
- finally
- {
- FunctionLoader.SetupWellKnownPaths(_functionLoadRequest);
- }
+ Assert.Single(_testLogger.FullLog);
+ Assert.Matches("Trace: No 'profile.ps1' is found at the FunctionApp root folder: ", _testLogger.FullLog[0]);
}
[Fact]
@@ -188,21 +193,11 @@ public void ProfileWithTerminatingError()
{
//initialize fresh log
_testLogger.FullLog.Clear();
- var funcLoadReq = _functionLoadRequest.Clone();
- funcLoadReq.Metadata.Directory = Path.Join(_functionDirectory, "ProfileWithTerminatingError", "Func1");
+ var profilePath = Path.Join(TestUtils.FunctionDirectory, "ProfileWithTerminatingError", "profile.ps1");
- try
- {
- FunctionLoader.SetupWellKnownPaths(funcLoadReq);
-
- Assert.Throws(() => _testManager.PerformRunspaceLevelInitialization());
- Assert.Single(_testLogger.FullLog);
- Assert.Matches("Error: Fail to run profile.ps1. See logs for detailed errors. Profile location: ", _testLogger.FullLog[0]);
- }
- finally
- {
- FunctionLoader.SetupWellKnownPaths(_functionLoadRequest);
- }
+ Assert.Throws(() => _testManager.InvokeProfile(profilePath));
+ Assert.Single(_testLogger.FullLog);
+ Assert.Matches("Error: Fail to run profile.ps1. See logs for detailed errors. Profile location: ", _testLogger.FullLog[0]);
}
[Fact]
@@ -210,22 +205,12 @@ public void ProfileWithNonTerminatingError()
{
//initialize fresh log
_testLogger.FullLog.Clear();
- var funcLoadReq = _functionLoadRequest.Clone();
- funcLoadReq.Metadata.Directory = Path.Join(_functionDirectory, "ProfileWithNonTerminatingError", "Func1");
-
- try
- {
- FunctionLoader.SetupWellKnownPaths(funcLoadReq);
- _testManager.PerformRunspaceLevelInitialization();
+ var profilePath = Path.Join(TestUtils.FunctionDirectory, "ProfileWithNonTerminatingError", "Profile.ps1");
+ _testManager.InvokeProfile(profilePath);
- Assert.Equal(2, _testLogger.FullLog.Count);
- Assert.Equal("Error: ERROR: help me!", _testLogger.FullLog[0]);
- Assert.Matches("Error: Fail to run profile.ps1. See logs for detailed errors. Profile location: ", _testLogger.FullLog[1]);
- }
- finally
- {
- FunctionLoader.SetupWellKnownPaths(_functionLoadRequest);
- }
+ Assert.Equal(2, _testLogger.FullLog.Count);
+ Assert.Equal("Error: ERROR: help me!", _testLogger.FullLog[0]);
+ Assert.Matches("Error: Fail to run profile.ps1. See logs for detailed errors. Profile location: ", _testLogger.FullLog[1]);
}
}
}
diff --git a/test/Unit/Utility/TypeExtensionsTests.cs b/test/Unit/Utility/TypeExtensionsTests.cs
index ebf1f9a7..bf9e19d7 100644
--- a/test/Unit/Utility/TypeExtensionsTests.cs
+++ b/test/Unit/Utility/TypeExtensionsTests.cs
@@ -26,7 +26,7 @@ public class TypeExtensionsTests
public TypeExtensionsTests()
{
_testLogger = new ConsoleLogger();
- _testManager = new PowerShellManager(_testLogger);
+ _testManager = TestUtils.NewTestPowerShellManager(_testLogger);
}
#region TypedDataToObject