public void DiscoverTests(IEnumerable <string> sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) { ChutzpahTracer.TraceInformation("Begin Test Adapter Discover Tests"); var settingsProvider = discoveryContext.RunSettings.GetSettings(ChutzpahAdapterSettings.SettingsName) as ChutzpahAdapterSettingsService; var settings = settingsProvider != null ? settingsProvider.Settings : new ChutzpahAdapterSettings(); ChutzpahTracingHelper.Toggle(settings.EnabledTracing); var testOptions = new TestOptions { TestFileTimeoutMilliseconds = settings.TimeoutMilliseconds, TestingMode = settings.TestingMode, MaxDegreeOfParallelism = settings.MaxDegreeOfParallelism }; IList <TestError> errors; var testCases = testRunner.DiscoverTests(sources, testOptions, out errors); ChutzpahTracer.TraceInformation("Sending discovered tests to test case discovery sink"); foreach (var testCase in testCases) { var vsTestCase = testCase.ToVsTestCase(); discoverySink.SendTestCase(vsTestCase); } foreach (var error in errors) { logger.SendMessage(TestMessageLevel.Error, RunnerCallback.FormatFileErrorMessage(error)); } ChutzpahTracer.TraceInformation("End Test Adapter Discover Tests"); }
public void RunTests(IEnumerable <string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle) { ChutzpahTracer.TraceInformation("Begin Test Adapter Run Tests"); var settingsProvider = runContext.RunSettings.GetSettings(ChutzpahAdapterSettings.SettingsName) as ChutzpahAdapterSettingsService; var settings = settingsProvider != null ? settingsProvider.Settings : new ChutzpahAdapterSettings(); ChutzpahTracingHelper.Toggle(settings.EnabledTracing); var testOptions = new TestOptions { TestFileTimeoutMilliseconds = settings.TimeoutMilliseconds, TestingMode = settings.TestingMode, MaxDegreeOfParallelism = settings.MaxDegreeOfParallelism, }; testOptions.CoverageOptions.Enabled = runContext.IsDataCollectionEnabled; var callback = new ParallelRunnerCallbackAdapter(new ExecutionCallback(frameworkHandle, runContext)); testRunner.RunTests(sources, testOptions, callback); ChutzpahTracer.TraceInformation("End Test Adapter Run Tests"); }
public void DiscoverTests(IEnumerable <string> sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink) { if (Environment.GetEnvironmentVariable("ATTACH_DEBUGGER_CHUTZPAH") != null) { Debugger.Launch(); } ChutzpahTracer.TraceInformation("Begin Test Adapter Discover Tests"); var settingsProvider = discoveryContext.RunSettings.GetSettings(AdapterConstants.SettingsName) as ChutzpahAdapterSettingsProvider; var settings = settingsProvider != null ? settingsProvider.Settings : new ChutzpahAdapterSettings(); ChutzpahTracingHelper.Toggle(settings.EnabledTracing); var testOptions = new TestOptions { MaxDegreeOfParallelism = settings.MaxDegreeOfParallelism, ChutzpahSettingsFileEnvironments = new ChutzpahSettingsFileEnvironments(settings.ChutzpahSettingsFileEnvironments) }; ChutzpahTracer.TraceInformation("Sending discovered tests to test case discovery sink"); var callback = new ParallelRunnerCallbackAdapter(new DiscoveryCallback(logger, discoverySink)); var testCases = testRunner.DiscoverTests(sources, testOptions, callback); ChutzpahTracer.TraceInformation("End Test Adapter Discover Tests"); }
/// <summary> /// Handler to react to project load/unload events. /// </summary> private void OnSolutionProjectChanged(object sender, SolutionEventsListenerEventArgs e) { if (e != null) { string projectPath = VsSolutionHelper.GetProjectPath(e.Project); var files = FindPotentialTestFiles(e.Project); if (e.ChangedReason == SolutionChangedReason.Load) { ChutzpahTracer.TraceInformation("Project Loaded: '{0}'", projectPath); UpdateChutzpahEnvironmentForProject(projectPath); UpdateTestContainersAndFileWatchers(files, true); } else if (e.ChangedReason == SolutionChangedReason.Unload) { ChutzpahTracer.TraceInformation("Project Unloaded: '{0}'", projectPath); RemoveChutzpahEnvironmentForProject(projectPath); UpdateTestContainersAndFileWatchers(files, false); } } // Do not fire OnTestContainersChanged here. // This will cause us to fire this event too early before the UTE is ready to process containers and will result in an exception. // The UTE will query all the TestContainerDiscoverers once the solution is loaded. }
public void PrepareTestHarnessForCoverage(TestHarness harness, IFrameworkDefinition definition, ChutzpahTestSettingsFile testSettingsFile) { string blanketScriptName = GetBlanketScriptName(definition, testSettingsFile); // Construct array of scripts to exclude from instrumentation/coverage collection. var filesToExcludeFromCoverage = new List <string>(); var filesToIncludeInCoverage = new List <string>(); var extensionMap = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); foreach (TestHarnessItem refScript in harness.ReferencedScripts.Where(rs => rs.HasFile)) { // Skip files which the user is asking us to exclude if (!IsFileEligibleForInstrumentation(refScript.ReferencedFile.Path)) { filesToExcludeFromCoverage.Add(refScript.Attributes["src"]); } else { refScript.Attributes["type"] = "text/blanket"; // prevent Phantom/browser parsing } // Build extension map for when we conver to regex the include/exclude patterns if (!string.IsNullOrEmpty(refScript.ReferencedFile.GeneratedFilePath)) { var sourceExtension = Path.GetExtension(refScript.ReferencedFile.Path); extensionMap[sourceExtension] = ".js"; } } // Construct array of scripts to exclude from instrumentation/coverage collection. filesToExcludeFromCoverage.AddRange( harness.TestFrameworkDependencies.Concat(harness.CodeCoverageDependencies) .Where(dep => dep.HasFile && IsScriptFile(dep.ReferencedFile)) .Select(dep => dep.Attributes["src"]) .Concat(excludePatterns.Select(f => ToRegex(f, extensionMap)))); filesToIncludeInCoverage.AddRange(includePatterns.Select(f => ToRegex(f, extensionMap))); // Name the coverage object so that the JS runner can pick it up. harness.ReferencedScripts.Add(new Script(string.Format("window.{0}='_$blanket';", Constants.ChutzpahCoverageObjectReference))); // Configure Blanket. TestHarnessItem blanketMain = harness.CodeCoverageDependencies.Single( d => d.Attributes.ContainsKey("src") && (d.Attributes["src"].EndsWith(blanketScriptName) || d.Attributes["src"].Contains(blanketScriptName + "?"))); string dataCoverNever = "[" + string.Join(",", filesToExcludeFromCoverage.Select(file => "'" + file + "'")) + "]"; string dataCoverOnly = filesToIncludeInCoverage.Any() ? "[" + string.Join(",", filesToIncludeInCoverage.Select(file => "'" + file + "'")) + "]" : "//.*/"; ChutzpahTracer.TraceInformation("Adding data-cover-never attribute to blanket: {0}", dataCoverNever); blanketMain.Attributes.Add("data-cover-flags", "ignoreError autoStart"); blanketMain.Attributes.Add("data-cover-only", dataCoverOnly); blanketMain.Attributes.Add("data-cover-never", dataCoverNever); blanketMain.Attributes.Add("data-cover-timeout", testSettingsFile.CodeCoverageTimeout.HasValue ? testSettingsFile.CodeCoverageTimeout.Value.ToString() : "5000"); }
public ChutzpahWebServerHost CreateServer(ChutzpahWebServerConfiguration configuration) { if (ChutzpahWebServerHost.ActiveWebServer != null && ChutzpahWebServerHost.ActiveWebServer.RootPath.Equals(configuration.RootPath, StringComparison.OrdinalIgnoreCase)) { // If the requested server is already running just re-use it return(ChutzpahWebServerHost.ActiveWebServer); } var hostConfiguration = new HostConfiguration { RewriteLocalhost = true, UrlReservations = new UrlReservations { CreateAutomatically = true } }; var port = GetNextAvailablePort(configuration.DefaultPort.Value); var builtInDependencyFolder = fileProbe.BuiltInDependencyDirectory; ChutzpahTracer.TraceInformation("Creating Web Server Host at path {0} and port {1}", configuration.RootPath, port); var host = new NancyHost(new Uri(string.Format("http://localhost:{0}", port)), new NancySettingsBootstrapper(configuration.RootPath, builtInDependencyFolder), hostConfiguration); host.Start(); var chutzpahWebServerHost = ChutzpahWebServerHost.Create(host, configuration.RootPath, port); return(chutzpahWebServerHost); }
/// <summary> /// After a project is loaded or unloaded either add or remove from the file watcher /// all test potential items inside that project /// </summary> private void UpdateTestContainersAndFileWatchers(IEnumerable <TestFileCandidate> files, bool isAdd) { ChutzpahTracer.TraceInformation("Begin UpdateTestContainersAndFileWatchers"); Parallel.ForEach(files, file => { try { if (isAdd) { ChutzpahTracer.TraceInformation("Adding watch on {0}", file.Path); testFilesUpdateWatcher.AddWatch(file.Path); AddTestContainerIfTestFile(file); } else { ChutzpahTracer.TraceInformation("Removing watch on {0}", file.Path); testFilesUpdateWatcher.RemoveWatch(file.Path); RemoveTestContainer(file); } } catch (Exception e) { ChutzpahTracer.TraceError(e, "Failed in UpdateTestContainersAndFileWatchers"); } }); ChutzpahTracer.TraceInformation("End UpdateTestContainersAndFileWatchers"); }
private IEnumerable <ITestContainer> GetTestContainers() { ChutzpahTracer.TraceInformation("Begin GetTestContainers"); logger.Log("GetTestContainers() are called", "ChutzpahTestContainerDiscoverer", LogType.Information); ChutzpahTracingHelper.Toggle(settingsMapper.Settings.EnabledTracing); if (initialContainerSearch || forceFullContainerRefresh) { ChutzpahTracer.TraceInformation("Begin Initial test container search"); logger.Log("Initial test container search", "ChutzpahTestContainerDiscoverer", LogType.Information); // Before the full container search we clear the settings cache to make sure // we are getting the latest version of the settings // If the user changes the settings file after this it will cause a full search again chutzpahTestSettingsService.ClearCache(); cachedContainers.Clear(); var jsFiles = FindPotentialTestFiles(); UpdateTestContainersAndFileWatchers(jsFiles, true); initialContainerSearch = false; forceFullContainerRefresh = false; ChutzpahTracer.TraceInformation("End Initial test container search"); } ChutzpahTracer.TraceInformation("End GetTestContainers"); return(cachedContainers.Values); }
public void RunTests(IEnumerable <string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle) { ChutzpahTracer.TraceInformation("Begin Test Adapter Run Tests"); var settingsProvider = runContext.RunSettings.GetSettings(AdapterConstants.SettingsName) as ChutzpahAdapterSettingsProvider; var settings = settingsProvider != null ? settingsProvider.Settings : new ChutzpahAdapterSettings(); ChutzpahTracingHelper.Toggle(settings.EnabledTracing); var testOptions = new TestOptions { TestLaunchMode = runContext.IsBeingDebugged ? TestLaunchMode.Custom: settings.OpenInBrowser ? TestLaunchMode.FullBrowser: TestLaunchMode.HeadlessBrowser, CustomTestLauncher = runContext.IsBeingDebugged ? new VsDebuggerTestLauncher() : null, MaxDegreeOfParallelism = runContext.IsBeingDebugged ? 1 : settings.MaxDegreeOfParallelism, ChutzpahSettingsFileEnvironments = new ChutzpahSettingsFileEnvironments(settings.ChutzpahSettingsFileEnvironments) }; testOptions.CoverageOptions.Enabled = runContext.IsDataCollectionEnabled; var callback = new ParallelRunnerCallbackAdapter(new ExecutionCallback(frameworkHandle, runContext)); testRunner.RunTests(sources, testOptions, callback); ChutzpahTracer.TraceInformation("End Test Adapter Run Tests"); }
private ChutzpahWebServerHost BuildHost(string rootPath, int defaultPort, string builtInDependencyFolder) { var attemptLimit = Constants.WebServerCreationAttemptLimit; var success = false; do { // We can try multiple times to build the webserver. The reason is there is a possible race condition where // between when we find a free port and when we start the server that port may have been taken. To mitigate this we // can retry to hopefully avoid this issue. attemptLimit--; var port = FindFreePort(defaultPort); try { ChutzpahTracer.TraceInformation("Creating Web Server Host at path {0} and port {1}", rootPath, port); var host = new WebHostBuilder() .UseUrls($"http://localhost:{port}") .UseContentRoot(rootPath) .UseWebRoot("") .UseKestrel() .Configure((app) => { var env = (IHostingEnvironment)app.ApplicationServices.GetService(typeof(IHostingEnvironment)); app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = AddFileCacheHeaders, ServeUnknownFileTypes = true, FileProvider = new ChutzpahServerFileProvider(env.ContentRootPath, builtInDependencyFolder) }); app.Run(async(context) => { if (context.Request.Path == "/") { await context.Response.WriteAsync($"Chutzpah Web Server (Version { Assembly.GetEntryAssembly().GetName().Version})"); } else { context.Response.StatusCode = (int)HttpStatusCode.NotFound; } }); }) .Build(); host.Start(); success = true; return(ChutzpahWebServerHost.Create(host, rootPath, port)); } catch (Exception ex) when(attemptLimit > 0) { ChutzpahTracer.TraceError(ex, "Unable to create web server host at path {0} and port {1}. Trying again...", rootPath, port); } }while (!success && attemptLimit > 0); throw new ChutzpahException("Failed to create web server. This should never be hit!"); }
/// <summary> /// Fire Events to Notify testcontainerdiscoverer listeners that containers have changed. /// This is the push notification VS uses to update the unit test window. /// /// The initialContainerSearch check is meant to prevent us from notifying VS about updates /// until it is ready /// </summary> private void OnTestContainersChanged() { ChutzpahTracer.TraceInformation("Begin OnTestContainersChanged"); if (TestContainersUpdated != null && !initialContainerSearch) { TestContainersUpdated(this, EventArgs.Empty); } ChutzpahTracer.TraceInformation("End OnTestContainersChanged"); }
public void Dispose() { try { ChutzpahTracer.TraceInformation("Tearing down Web Server Host at path {0} and port {1}", RootPath, Port); IsRunning = false; WebHost.Dispose(); } catch (Exception e) { ChutzpahTracer.TraceError(e, "Error tearing down Web Server Host at path {0} and port {1}", RootPath, Port); } }
/// <summary> /// Handler to react to test file Add/remove/rename andcontents changed events /// </summary> private void OnProjectItemChanged(object sender, TestFileChangedEventArgs e) { ChutzpahTracer.TraceInformation("Begin OnProjectItemChanged"); if (e != null) { // If a chutzpah.json file changed then we set the flag to // ensure next time get if (fileProbe.IsChutzpahSettingsFile(e.File.Path)) { forceFullContainerRefresh = true; return; } // Don't do anything for files we are sure can't be test files if (!HasTestFileExtension(e.File.Path)) { return; } logger.Log(string.Format("Changed detected for {0} with change type of {1}", e.File, e.ChangedReason), "ChutzpahTestContainerDiscoverer", LogType.Information); switch (e.ChangedReason) { case TestFileChangedReason.Added: ChutzpahTracer.TraceInformation("Adding watch on {0}", e.File.Path); testFilesUpdateWatcher.AddWatch(e.File.Path); AddTestContainerIfTestFile(e.File); break; case TestFileChangedReason.Removed: ChutzpahTracer.TraceInformation("Removing watch on {0}", e.File.Path); testFilesUpdateWatcher.RemoveWatch(e.File.Path); RemoveTestContainer(e.File); break; case TestFileChangedReason.Changed: AddTestContainerIfTestFile(e.File); break; } OnTestContainersChanged(); } ChutzpahTracer.TraceInformation("End OnProjectItemChanged"); }
private IEnumerable <TestFileCandidate> FindPotentialTestFiles() { try { ChutzpahTracer.TraceInformation("Begin enumerating loaded projects for test files"); var solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution)); var loadedProjects = solution.EnumerateLoadedProjects(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION).OfType <IVsProject>(); return(loadedProjects.SelectMany(FindPotentialTestFiles).ToList()); } finally { ChutzpahTracer.TraceInformation("End enumerating loaded projects for test files"); } }
/// <summary> /// Will remove a test container for a given file path /// </summary> /// <param name="file"></param> private void RemoveTestContainer(TestFileCandidate file) { // If a settings file don't add a container if (fileProbe.IsChutzpahSettingsFile(file.Path)) { return; } ITestContainer container; var res = cachedContainers.TryRemove(file.Path, out container); if (res) { ChutzpahTracer.TraceInformation("Removed test container for '{0}'", file.Path); } }
/// <summary> /// Will remove a test container for a given file path /// </summary> /// <param name="file"></param> private void RemoveTestContainer(TestFileCandidate file) { // If a settings file don't add a container if (fileProbe.IsChutzpahSettingsFile(file.Path)) { return; } var index = cachedContainers.FindIndex(x => x.Source.Equals(file.Path, StringComparison.OrdinalIgnoreCase)); if (index >= 0) { ChutzpahTracer.TraceInformation("Removed test container for '{0}'", file.Path); cachedContainers.RemoveAt(index); } }
public void Dispose() { try { ChutzpahTracer.TraceInformation("Tearing down Web Server Host at path {0} and port {1}", RootPath, Port); NancyHost.Dispose(); } catch (Exception e) { ChutzpahTracer.TraceError(e, "Error tearing down Web Server Host at path {0} and port {1}", RootPath, Port); } finally { // Set active server to null Interlocked.Exchange(ref activeWebServer, null); } }
public void PrepareTestHarnessForCoverage(TestHarness harness, IFrameworkDefinition definition, ChutzpahTestSettingsFile testSettingsFile) { string blanketScriptName = GetBlanketScriptName(definition, testSettingsFile); // Construct array of scripts to exclude from instrumentation/coverage collection. var filesToExcludeFromCoverage = harness.TestFrameworkDependencies.Concat(harness.CodeCoverageDependencies) .Where(dep => dep.HasFile && IsScriptFile(dep.ReferencedFile)) .Select(dep => dep.Attributes["src"]) .Concat(excludePatterns.Select(ToRegex)) .ToList(); var filesToIncludeInCoverage = includePatterns.Select(ToRegex).ToList(); foreach (TestHarnessItem refScript in harness.ReferencedScripts.Where(rs => rs.HasFile)) { // Exclude files which the user is asking us to ignores if (!IsFileEligibleForInstrumentation(refScript.ReferencedFile.Path)) { filesToExcludeFromCoverage.Add(refScript.Attributes["src"]); } else { refScript.Attributes["type"] = "text/blanket"; // prevent Phantom/browser parsing } } // Name the coverage object so that the JS runner can pick it up. harness.ReferencedScripts.Add(new Script(string.Format("window.{0}='_$blanket';", Constants.ChutzpahCoverageObjectReference))); // Configure Blanket. TestHarnessItem blanketMain = harness.CodeCoverageDependencies.Single( d => d.Attributes.ContainsKey("src") && d.Attributes["src"].EndsWith(blanketScriptName)); string dataCoverNever = "[" + string.Join(",", filesToExcludeFromCoverage.Select(file => "'" + file + "'")) + "]"; string dataCoverOnly = filesToIncludeInCoverage.Any() ? "[" + string.Join(",", filesToIncludeInCoverage.Select(file => "'" + file + "'")) + "]" : "//.*/"; ChutzpahTracer.TraceInformation("Adding data-cover-never attribute to blanket: {0}", dataCoverNever); blanketMain.Attributes.Add("data-cover-flags", "ignoreError autoStart"); blanketMain.Attributes.Add("data-cover-only", dataCoverOnly); blanketMain.Attributes.Add("data-cover-never", dataCoverNever); }
/// <summary> /// Adds a test container for the given file if it is a test file. /// This will first remove any existing container for that file /// </summary> /// <param name="file"></param> private void AddTestContainerIfTestFile(TestFileCandidate file) { // If a settings file don't add a container if (fileProbe.IsChutzpahSettingsFile(file.Path)) { return; } var isTestFile = IsTestFile(file.Path); RemoveTestContainer(file); // Remove if there is an existing container if (isTestFile) { ChutzpahTracer.TraceInformation("Added test container for '{0}'", file.Path); var container = new JsTestContainer(this, file.Path.ToLowerInvariant(), AdapterConstants.ExecutorUri); cachedContainers[container.Source] = container; } }
/// <summary> /// Comapre this test container to another one /// They are the same if same source and timestamp /// </summary> /// <param name="other"></param> /// <returns></returns> public int CompareTo(ITestContainer other) { var testContainer = other as JsTestContainer; if (testContainer == null) { return(-1); } var result = String.Compare(this.Source, testContainer.Source, StringComparison.OrdinalIgnoreCase); if (result != 0) { return(result); } ChutzpahTracer.TraceInformation("Test container comparision {0} vs {1} for {2}", this.timeStamp, testContainer.timeStamp, this.Source); return(this.timeStamp.CompareTo(testContainer.timeStamp)); }
private IEnumerable <TestFileCandidate> FindPotentialTestFiles(IVsProject project) { string projectPath = VsSolutionHelper.GetProjectPath(project); try { ChutzpahTracer.TraceInformation("Begin selecting potential test files from project '{0}'", projectPath); return((from item in VsSolutionHelper.GetProjectItems(project) let hasTestExtension = HasTestFileExtension(item) let isChutzpahSettingsFile = fileProbe.IsChutzpahSettingsFile(item) where !fileProbe.IsTemporaryChutzpahFile(item) && (hasTestExtension || isChutzpahSettingsFile) select new TestFileCandidate { Path = item }).ToList()); } finally { ChutzpahTracer.TraceInformation("End selecting potential test files from project '{0}'", projectPath); } }
/// <summary> /// After a project is loaded or unloaded either add or remove from the file watcher /// all test potential items inside that project /// </summary> private void UpdateTestContainersAndFileWatchers(IEnumerable <TestFileCandidate> files, bool isAdd) { ChutzpahTracer.TraceInformation("Begin UpdateTestContainersAndFileWatchers"); foreach (var file in files) { if (isAdd) { ChutzpahTracer.TraceInformation("Adding watch on {0}", file.Path); testFilesUpdateWatcher.AddWatch(file.Path); AddTestContainerIfTestFile(file); } else { ChutzpahTracer.TraceInformation("Removing watch on {0}", file.Path); testFilesUpdateWatcher.RemoveWatch(file.Path); RemoveTestContainer(file); } } ChutzpahTracer.TraceInformation("End UpdateTestContainersAndFileWatchers"); }
public override void TestSuiteFinished(TestCaseSummary testResultsSummary) { base.TestSuiteFinished(testResultsSummary); if (!runContext.IsDataCollectionEnabled || testResultsSummary.CoverageObject == null) { return; } try { // If we do not have a solutiondirectory, we assume that we are running in tfs build // In that case we only write to the testrundirectory and do not open a browser if (string.IsNullOrEmpty(runContext.SolutionDirectory)) { ChutzpahTracer.TraceInformation("Chutzpah runs in TFSBuild, writing coverage file to {0}", runContext.TestRunDirectory); var directory = runContext.TestRunDirectory; CoverageOutputGenerator.WriteHtmlFile(directory, testResultsSummary.CoverageObject); CoverageOutputGenerator.WriteJsonFile(directory, testResultsSummary.CoverageObject); } else { ChutzpahTracer.TraceInformation("Chutzpah runs not in TFSBuild opening coverage file in browser"); var directory = runContext.SolutionDirectory; var coverageHtmlFile = CoverageOutputGenerator.WriteHtmlFile(directory, testResultsSummary.CoverageObject); var processHelper = new ProcessHelper(); processHelper.LaunchFileInBrowser(coverageHtmlFile); } } catch (Exception e) { frameworkHandle.SendMessage(TestMessageLevel.Error, string.Format("Error while writing coverage output: {0}", e)); } }
public void Compile(IEnumerable <TestContext> testContexts) { // Group the test contexts by test settings to run batch aware settings like compile // For each test settings file that defines a compile step we will run it and update // testContexts reference files accordingly. var groupedTestContexts = testContexts.GroupBy(x => x.TestFileSettings); foreach (var contextGroup in groupedTestContexts) { var testSettings = contextGroup.Key; // If there is no compile setting then nothing to do here if (testSettings.Compile == null) { continue; } // Build the mapping from source to output files and gather properties about them var filePropeties = ( from file in contextGroup.SelectMany(x => x.ReferencedFiles).Distinct() where testSettings.Compile.Extensions.Any(x => file.Path.EndsWith(x, StringComparison.OrdinalIgnoreCase)) let sourceProperties = GetFileProperties(file.Path) let sourceHasOutput = !testSettings.Compile.ExtensionsWithNoOutput.Any(x => file.Path.EndsWith(x, StringComparison.OrdinalIgnoreCase)) let outputPath = GetOutputPath(file.Path, testSettings.Compile) let outputProperties = sourceHasOutput ? GetFileProperties(outputPath) : null select new SourceCompileInfo { SourceProperties = sourceProperties, OutputProperties = outputProperties, SourceHasOutput = sourceHasOutput }).ToList(); var outputPathMap = filePropeties .Where(x => x.SourceHasOutput) .ToDictionary(x => x.SourceProperties.Path, x => x.OutputProperties.Path, StringComparer.OrdinalIgnoreCase); // Check if the batch compile is needed var shouldCompile = CheckIfCompileIsNeeded(testSettings, filePropeties); // Run the batch compile if necessary if (shouldCompile) { if (testSettings.Compile.Mode == BatchCompileMode.Executable) { RunBatchCompile(testSettings); } else if (testSettings.Compile.Mode == BatchCompileMode.External) { ChutzpahTracer.TraceError("Chutzpah determined generated .js files are missing but the compile mode is External so Chutzpah can't compile them. Test results may be wrong."); } } else { ChutzpahTracer.TraceInformation("Skipping batch compile since all files are supdate to date for {0}", testSettings.SettingsFileName); } // Now that compile finished set generated path on all files who match the compiled extensions var filesToUpdate = contextGroup.SelectMany(x => x.ReferencedFiles) .Where(x => outputPathMap.ContainsKey(x.Path)); foreach (var file in filesToUpdate) { var outputPath = outputPathMap[file.Path]; if (outputPath != null && fileSystem.FileExists(outputPath)) { file.GeneratedFilePath = outputPath; ChutzpahTracer.TraceInformation("Found generated path for {0} at {1}", file.Path, outputPath); } else { ChutzpahTracer.TraceWarning("Couldn't find generated path for {0} at {1}", file.Path, outputPath); } if (!string.IsNullOrWhiteSpace(file.GeneratedFilePath)) { file.SourceMapFilePath = testSettings.Compile.UseSourceMaps ? sourceMapDiscoverer.FindSourceMap(file.GeneratedFilePath) : null; } } } }
public void Compile(IEnumerable <TestContext> testContexts, ITestMethodRunnerCallback callback = null) { // Group the test contexts by test settings to run batch aware settings like compile // For each test settings file that defines a compile step we will run it and update // testContexts reference files accordingly. var groupedTestContexts = testContexts.GroupBy(x => x.TestFileSettings); foreach (var contextGroup in groupedTestContexts) { var testSettings = contextGroup.Key; // If there is no compile setting then nothing to do here if (testSettings.Compile == null) { continue; } // Build the mapping from source to output files and gather properties about them var filePropeties = ( from file in contextGroup.SelectMany(x => x.ReferencedFiles).Where(x => !x.IsBuiltInDependency).Distinct() where testSettings.Compile.Extensions.Any(x => file.Path.EndsWith(x, StringComparison.OrdinalIgnoreCase)) let sourceProperties = GetFileProperties(file.Path) let sourceHasOutput = !testSettings.Compile.ExtensionsWithNoOutput.Any(x => file.Path.EndsWith(x, StringComparison.OrdinalIgnoreCase)) let outputPath = GetOutputPath(file.Path, testSettings.Compile) let outputProperties = sourceHasOutput ? GetFileProperties(outputPath) : null select new SourceCompileInfo { SourceProperties = sourceProperties, OutputProperties = outputProperties, SourceHasOutput = sourceHasOutput }).ToList(); var outputPathMap = filePropeties .Where(x => x.SourceHasOutput) .ToDictionary(x => x.SourceProperties.Path, x => x.OutputProperties.Path, StringComparer.OrdinalIgnoreCase); // Check if the batch compile is needed var shouldCompile = CheckIfCompileIsNeeded(testSettings, filePropeties); // Run the batch compile if necessary if (shouldCompile) { if (testSettings.Compile.Mode == BatchCompileMode.Executable) { RunBatchCompile(testSettings); } else { ChutzpahTracer.TraceWarning("Chutzpah determined generated .js files are missing but the compile mode is External so Chutzpah can't compile them. Test results may be wrong."); } } else { ChutzpahTracer.TraceInformation("Skipping batch compile since all files are update to date for {0}", testSettings.SettingsFileName); } // Now that compile finished set generated path on all files who match the compiled extensions var filesToUpdate = contextGroup.SelectMany(x => x.ReferencedFiles) .Where(x => outputPathMap.ContainsKey(x.Path)); foreach (var file in filesToUpdate) { var outputPath = outputPathMap[file.Path]; if (outputPath != null && fileSystem.FileExists(outputPath)) { file.GeneratedFilePath = outputPath; ChutzpahTracer.TraceInformation("Found generated path for {0} at {1}", file.Path, outputPath); } else { // If we could not find the file at the configured path attempt to see if it co-located ChutzpahTracer.TraceInformation("Unable to find generated path at configured location so attempting to see if generated file is co-located."); var coLocatedOutputPath = Path.ChangeExtension(file.Path, ".js"); if (fileSystem.FileExists(coLocatedOutputPath)) { file.GeneratedFilePath = coLocatedOutputPath; ChutzpahTracer.TraceInformation("Found generated path for {0} at {1}", file.Path, coLocatedOutputPath); } else { var error = string.Format("Couldn't find generated path for {0} at {1} or at {2}", file.Path, outputPath, coLocatedOutputPath); ChutzpahTracer.TraceError(error); if (!testSettings.Compile.IgnoreMissingFiles.GetValueOrDefault()) { // Throw and fail here since if we cant find the file we cannot be sure anything will run var exception = new FileNotFoundException(error, outputPath); callback.ExceptionThrown(exception, outputPath); } } } if (!string.IsNullOrWhiteSpace(file.GeneratedFilePath)) { file.SourceMapFilePath = testSettings.Compile.UseSourceMaps.GetValueOrDefault() ? sourceMapDiscoverer.FindSourceMap(file.GeneratedFilePath) : null; } } } }
/// <summary> /// The solution was unloaded so we need to indicate that next time containers are requested we do a full search /// </summary> private void SolutionListenerOnSolutionUnloaded(object sender, EventArgs eventArgs) { ChutzpahTracer.TraceInformation("Solution Unloaded..."); initialContainerSearch = true; }