/// <summary> /// Removes the cached runner for the specified run and configuration. /// </summary> /// <param name="run">The run.</param> /// <param name="configurationSubstitutions">The configuration substitutions.</param> public void Remove(TestRun run, DistributedConfigurationSubstitutions configurationSubstitutions = null) { ConcurrentDictionary<int, TestRunnerMetadata> byParametersCache; if (configurationSubstitutions == null ? cache.TryRemove(GetKey(run), out byParametersCache) : cache.TryGetValue(GetKey(run), out byParametersCache)) { IEnumerable<TestRunnerMetadata> toFree = new TestRunnerMetadata[0]; if (configurationSubstitutions != null) { TestRunnerMetadata metadata; if (byParametersCache.TryRemove(GetKeyForSubstitutions(configurationSubstitutions), out metadata)) { toFree = new[] { metadata }; } } else toFree = byParametersCache.Values; foreach (var metadata in toFree) { metadata.Runner.Unload(); metadata.Runner.Dispose(); } } }
/// <summary> /// Gets the or load. /// </summary> /// <param name="testRun">The test run.</param> /// <param name="substitutions"></param> /// <param name="loadRunner">The action.</param> /// <returns></returns> public NDistribUnitProcessRunner GetOrLoad(TestRun testRun, DistributedConfigurationSubstitutions substitutions, Func<NDistribUnitProcessRunner> loadRunner) { var key = GetKey(testRun); //var timeSpan = TimeSpan.FromHours(4); var cacheByParameters = cache.GetOrAdd(key, guid => new ConcurrentDictionary<int, TestRunnerMetadata>()); int hashCode = GetKeyForSubstitutions(substitutions); var metadata = cacheByParameters.GetOrAdd(hashCode, hash => { var runner = loadRunner(); // var timer = new Timer(obj=> // { // TestRunnerMetadata removed; // if (cacheByParameters.TryRemove(hashCode, out removed)) // removed.Runner.Unload(); // }, null, timeSpan, TimeSpan.FromMilliseconds(-1)); return new TestRunnerMetadata { Runner = runner, // Timer = timer }; }); // metadata.Timer.Change(timeSpan, TimeSpan.FromMilliseconds(-1)); return metadata.Runner; }
/// <summary> /// Gets the substituted configuration file. /// </summary> /// <param name="project">The project.</param> /// <param name="nUnitParameters">The n unit parameters.</param> /// <param name="configurationSubstitutions">The configuration substitutions.</param> /// <returns></returns> public string GetSubstitutedConfigurationFile(TestProject project, NUnitParameters nUnitParameters, DistributedConfigurationSubstitutions configurationSubstitutions) { var distributedConfigurationFileNames = GetDistributedConfigurationFileNames(project, nUnitParameters); if (configurationSubstitutions == null || configurationSubstitutions.Variables.Count == 0) return distributedConfigurationFileNames.FirstOrDefault(); string finalConfigurationFile = null; int hashCode = configurationSubstitutions.GetHashCode(); foreach (var configurationFileName in distributedConfigurationFileNames) { var configurationDocument = XDocument.Load(configurationFileName); SubstitutePlaceHolders(configurationDocument, configurationSubstitutions); var substitutedFileName = Path.ChangeExtension(configurationFileName, string.Format(".{0}.config", hashCode)); configurationDocument.Save(substitutedFileName); if (finalConfigurationFile == null) finalConfigurationFile = substitutedFileName; } return finalConfigurationFile; }
private static int GetKeyForSubstitutions(DistributedConfigurationSubstitutions substitutions) { return substitutions == null ? 0 : substitutions.GetHashCode(); }
/// <summary> /// Runs tests on agent /// </summary> /// <param name="test">The test.</param> /// <param name="configurationSubstitutions">The configuration substitutions.</param> /// <returns></returns> public TestResult RunTests(TestUnit test, DistributedConfigurationSubstitutions configurationSubstitutions) { throw new CommunicationException(); }
internal void SubstitutePlaceHolders(XDocument doc, DistributedConfigurationSubstitutions configurationSubstitutions) { var descendantComments = doc.DescendantNodes().OfType<XComment>(); var comments = (from comment in descendantComments let match = TypeIdentifier.Match(comment.Value) where match.Success select new { XComment = comment, Match = match }).ToList(); foreach (var comment in comments) { if (!comment.Match.Groups["selector"].Value.Equals("Replace")) continue; var parameters = new JavaScriptSerializer().DeserializeObject(comment.Match.Groups["object"].Value) as Dictionary<string, object>; var xPath = parameters["XPath"] as string; var value = parameters["Value"] as string; var node = (comment.XComment.XPathEvaluate(xPath) as IEnumerable<object>).FirstOrDefault(); if (node is XElement) ((XElement)node).SetValue(value); else if (node is XAttribute) ((XAttribute)node).SetValue(value); comment.XComment.Remove(); } var xmlAsString = doc.ToString(); // not efficient but clear and ok for usually small configs xmlAsString = Variable.Replace(xmlAsString, match => { var variableName = match.Groups["varName"].Value; var variable = configurationSubstitutions.Variables .FirstOrDefault(v => v.Name.Equals(variableName)); if (variable == null) return match.Value; return variable.Value; }); xmlAsString = Escaping.Replace(xmlAsString, string.Empty); var doc2 = XDocument.Parse(xmlAsString); doc.ReplaceNodes(doc2.Nodes()); }
/// <summary> /// Gets the NUnit test result. /// </summary> /// <param name="test">The test.</param> /// <param name="project">The project.</param> /// <param name="configurationSubstitutions">The configuration substitutions.</param> /// <param name="isChild">if set to <c>true</c> [is child].</param> /// <returns></returns> public TestResult GetNUnitTestResult(TestUnit test, TestProject project, DistributedConfigurationSubstitutions configurationSubstitutions, bool isChild = false) { var nativeRunner = runnerCache.GetOrLoad(test.Run, configurationSubstitutions, () => { initializer.Initialize(); string configurationFileName = configurationOperator.GetSubstitutedConfigurationFile(project, test.Run. NUnitParameters, configurationSubstitutions); var mappedAssemblyFile = Path.Combine(project.Path, Path.GetFileName( test.Run.NUnitParameters. AssembliesToTest[0])); TestPackage package; if (!NUnitProject.IsNUnitProjectFile(mappedAssemblyFile)) package = new TestPackage(mappedAssemblyFile); else { var nunitProject = new NUnitProject(mappedAssemblyFile); nunitProject.Load(); if (!string.IsNullOrEmpty(test.Run.NUnitParameters.Configuration)) nunitProject.SetActiveConfig(test.Run.NUnitParameters.Configuration); package = nunitProject.ActiveConfig.MakeTestPackage(); } package.Settings["ShadowCopyFiles"] = true; package.AutoBinPath = true; if (!string.IsNullOrEmpty(configurationFileName)) { package.ConfigurationFile = configurationFileName; } var nativeTestRunner = new NDistribUnitProcessRunner(log); nativeTestRunner.Load(package); return nativeTestRunner; }); var testOptions = test.Run.NUnitParameters; TestResult testResult = null; try { Action runTest = ()=> { try { testResult = nativeRunner.Run(new NullListener(), new NUnitTestsFilter( testOptions.IncludeCategories, testOptions.ExcludeCategories, test.UniqueTestId).NativeFilter); nativeRunner.CleanupAfterRun(); } //TODO: remove this. This is for tracking purposes only catch(AppDomainUnloadedException ex) { log.Warning("AppDomainUnloadedException is still being thrown", ex); if (!isChild) { runnerCache.Remove(test.Run, configurationSubstitutions); testResult = GetNUnitTestResult(test, project, configurationSubstitutions, isChild: true); } else throw; } }; if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA && !Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA)) { var thread = new Thread(()=> exceptionCatcher.Run(runTest)); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); } else { runTest(); } } catch (Exception ex) { log.Error("Error while running test on agent", ex); throw; } return testResult; }
/// <summary> /// Runs the specified test. /// </summary> /// <param name="test">The test.</param> /// <param name="configurationSubstitutions"></param> /// <returns></returns> public TestResult Run(TestUnit test, DistributedConfigurationSubstitutions configurationSubstitutions) { var project = projects.Get(test.Run); if (project == null) { log.Info("Project file was not found. Throwing exception"); return TestResultFactory.GetProjectRetrievalFailure(test); } log.BeginActivity("Starting test execution..."); var nUnitTestResult = GetNUnitTestResult(test, project, configurationSubstitutions); log.EndActivity("Test execution was finished"); return nUnitTestResult; }
private DistributedConfigurationSubstitutions GetConfigurationValues(DistributedConfigurationSetup configurationSetup, AgentMetadata agentToRun, TestUnitWithMetadata testToRun) { //BUG: add dependency on test run! return configurations.GetOrAdd(agentToRun.Name, key => { var distributedConfigurationValues = new DistributedConfigurationSubstitutions(); foreach (var variable in configurationSetup.Variables) { distributedConfigurationValues.Variables.Add( new DistributedConfigurationVariablesValue(variable.Name, variable.GetNextValue())); } return distributedConfigurationValues; }); }
/// <summary> /// Runs tests on agent /// </summary> /// <param name="test">The test.</param> /// <param name="configurationSubstitutions">The configuration substitutions.</param> /// <returns></returns> public TestResult RunTests(TestUnit test, DistributedConfigurationSubstitutions configurationSubstitutions) { if (IsStarted) return TestRunner.RunTests(test, configurationSubstitutions); throw new CommunicationException("Agent seems to be not available"); }
// Should be started in a separate thread private void Run(TestUnitWithMetadata test, AgentMetadata agent, DistributedConfigurationSubstitutions configurationSubstitutions) { log.BeginActivity(string.Format("[{0}] Started test {1}", test.Test.Run, test.Test.Info.TestName.FullName)); var request = requests.GetBy(test.Test.Run); if (request != null) request.Status = TestRunRequestStatus.Pending; try { log.BeginActivity(string.Format("Connecting to agent [{0}]...", agent)); var testRunnerAgent = connectionProvider.GetConnection<IAgent>(agent.Address); log.BeginActivity(string.Format("Connected to agent [{0}]", agent)); log.BeginActivity(string.Format("Checking project existence ('{0}') on agent {1}", test.Test.Run, agent)); if (!testRunnerAgent.HasProject(test.Test.Run)) { log.EndActivity(string.Format("Project '{0}' doesn't exist on agent {1}", test.Test.Run, agent)); log.BeginActivity(string.Format("Sending project ('{0}') to agent {1}", test.Test.Run, agent)); using (Stream project = projects.GetStreamToPacked(test.Test.Run) ?? new MemoryStream(1)) { testRunnerAgent.ReceiveProject(new ProjectMessage { TestRun = test.Test.Run, Project = project }); } log.EndActivity(string.Format("Sent project ('{0}') to agent {1}", test.Test.Run, agent)); } else log.EndActivity(string.Format("Project '{0}' exist on agent {1}", test.Test.Run, agent)); var reprocessedCount = request.Statistics.ReprocessedCount; log.BeginActivity(string.Format("[{3}/{4}]: Running {0} on {1} with variables ({2})...", test.Test.UniqueTestId, agent, configurationSubstitutions, request.Statistics.GetCountAndIncrement(), reprocessedCount == 0 ? request.Statistics.Total.ToString(CultureInfo.InvariantCulture) : string.Format("{2}({0}+{1})", request.Statistics.Total, reprocessedCount, request.Statistics.Total + reprocessedCount))); TestResult result = testRunnerAgent.RunTests(test.Test, configurationSubstitutions); log.EndActivity(string.Format("Finished running {0} on {1}...", test.Test.UniqueTestId, agent)); if (result == null) throw new FaultException("Result is not available"); ProcessResult(test, agent, result); } catch (FaultException ex) { log.Error(string.Format("Exception while running test {0} on {1}", test.Test.UniqueTestId, agent), ex); agents.MarkAsFailure(agent); tests.Add(test); } catch (CommunicationException ex) { log.Error(string.Format("Exception in communication while running test {0} on {1}", test.Test.UniqueTestId, agent), ex); agents.MarkAsDisconnected(agent); tests.Add(test); } catch(Exception ex) { log.Error(string.Format("Something bad and unhandled happened while running test {0} on {1}", test.Test.UniqueTestId, agent), ex); } }