Skip to content

Tell the user when a test extension fails to load - #16390

Open
Jakub Jareš (nohwnd) wants to merge 3 commits into
microsoft:mainfrom
nohwnd:nohwnd-warn-when-test-extension-fails-to-load
Open

Tell the user when a test extension fails to load#16390
Jakub Jareš (nohwnd) wants to merge 3 commits into
microsoft:mainfrom
nohwnd:nohwnd-warn-when-test-extension-fails-to-load

Conversation

@nohwnd

@nohwnd Jakub Jareš (nohwnd) commented Aug 19, 2026

Copy link
Copy Markdown
Member

Only FileLoadException reached the user. Every other reason a file could not be loaded - BadImageFormatException, a missing dependency, a wrong target framework - went to the diag log only, so the extension was silently missing and the tests it provides silently did not run. That is the worst failure mode we have: a green run that ran nothing.

Now any failure to load an extension file is a warning, and it carries the reason instead of only pointing at /diag:

Failed to load extensions from file 'C:\repro\Broken.TestAdapter.dll'. Reason: Could not load file or assembly 'Broken.TestAdapter' or one of its dependencies. The system cannot find the file specified.

An assembly where GetTypes throws and not a single type comes out is reported the same way. When some types do load we stay quiet - the extension usually still works, and the types that failed are usually not extensions at all. That is deliberate, #290 was about exactly that noise.

Two things keep it quiet in normal runs:

  • The same file is scanned once per extension type we look for (adapters, loggers, data collectors, settings providers). The warning is emitted once per file, not four times.
  • The two C++ UWP files we probe for when there are no extension paths at all are not expected to be present, so failing to load them reports nothing.

The warning is once per run, not once per process. It used to be deduplicated on UnloadableFiles, which is static and never cleared, so in design mode, where an editor keeps the runner alive across many discovery and run requests, the user heard about a broken extension on the first request and then never again. UnloadableFiles now answers only whether loading the file is worth another try, a separate ReportedFiles answers whether the user has already seen the warning, and ClearExtensions() empties both. The runner calls that before every discovery and run request. Clearing the first one also means an extension whose missing dependency has since appeared is tried again on the next request, instead of staying skipped until the process exits.

I also split the assembly load from the assembly inspection in GetTestExtensionsFromFiles. Before, one catch covered both, so a broken TestPluginInformation constructor looked the same as a file that could not be loaded. Inspection failures stay in the diag log as before.

FailedToLoadAdapaterFile is replaced by FailedToLoadExtensionFile, which takes the reason. The old string is gone from the resx and the xlf files.

Verified against a real run: a folder with a Broken.TestAdapter.dll that is not a managed assembly prints the warning once through vstest.console /lt, and the same run without it prints no new warnings.

Added tests for the warning, for the once-per-file behavior, for reporting again after the extension cache is cleared, for the casing of the path, for staying quiet on a partial type load, and for the probe files staying quiet.

Supersedes #16382, which fixed the same issue. Its once-per-run reset and its casing test are carried over here.

🤖

Only FileLoadException reached the user. Every other reason a file could not be
loaded - BadImageFormatException, a missing dependency, a wrong target framework -
went to the diag log only, so the extension was silently missing and the tests it
provides silently did not run.

Now any failure to load an extension file is a warning, and it carries the reason
instead of only pointing at /diag. An assembly where not a single type can be
loaded is reported the same way; when some types do load we stay quiet, because
the extension usually still works.

The warning is emitted once per file, not once per extension type we scan for,
and the C++ UWP probe files stay quiet because they are not expected to be there.

🤖
Copilot AI lite review requested due to automatic review settings August 19, 2026 16:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves VSTest’s extension discovery so that any failure to load an extension assembly (not just FileLoadException) becomes a user-visible warning (including the failure reason), preventing “green runs” where tests are silently skipped due to missing/broken extensions.

Changes:

  • Emit a warning (once per extension file) when an extension assembly cannot be loaded, including the failure reason; keep the “known probe” assemblies quiet.
  • Separate “assembly load” failures (warn user) from “inspection” failures (stay in diag log) and handle the “no types could be loaded” case as a warning.
  • Add unit tests covering warning emission, “warn once”, and the known-probe quiet behavior; update localized resources.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs Adds coverage for warning emission, deduping per file, and known-probe quiet behavior.
src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginDiscoverer.cs Implements warning-on-load-failure behavior and once-per-file suppression, plus known-probe quiet mode.
src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginCache.cs Updates resource preloading to the new localized string key.
src/Microsoft.TestPlatform.Common/Resources/Resources.resx Replaces the old warning string with a new one that includes a reason placeholder.
src/Microsoft.TestPlatform.Common/Resources/Resources.Designer.cs Regenerates resource accessor for the renamed string key.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.cs.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.de.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.es.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.fr.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.it.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.ja.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.ko.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.pl.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.pt-BR.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.ru.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.tr.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.zh-Hans.xlf Updates localized resource entry for the new warning string.
src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.zh-Hant.xlf Updates localized resource entry for the new warning string.
Files not reviewed (1)
  • src/Microsoft.TestPlatform.Common/Resources/Resources.Designer.cs: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginDiscoverer.cs Outdated
Comment thread src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.de.xlf
Comment thread src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.fr.xlf
Comment thread src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.es.xlf
Comment thread src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.it.xlf
Comment thread src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.ru.xlf
Comment thread src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.tr.xlf
Comment thread src/Microsoft.TestPlatform.Common/Resources/xlf/Resources.cs.xlf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 69459b25-03f1-41a7-979d-9d401036d5a2
Copilot AI review requested due to automatic review settings August 19, 2026 17:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • src/Microsoft.TestPlatform.Common/Resources/Resources.Designer.cs: Generated file
Suppressed comments (2)

src/Microsoft.TestPlatform.Common/ExtensionFramework/TestPluginDiscoverer.cs:32

  • UnloadableFiles is used to ensure the warning is emitted only once per extension file, but the dictionary uses the default (case-sensitive) string comparer. On Windows, the same path can appear with different casing (and elsewhere in this repo extension paths are deduped with StringComparer.OrdinalIgnoreCase), which would lead to duplicate loads/warnings for what is effectively the same file.
    /// <summary>
    /// Files that we already failed to load. The same file is scanned once per extension type we look for
    /// (test adapters, loggers, data collectors, settings providers, ...), so this both avoids repeating a load
    /// that is known to fail, and makes sure the user is told about the failure only once.
    /// </summary>
    private static readonly ConcurrentDictionary<string, object?> UnloadableFiles = new();

test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestPluginDiscovererTests.cs:148

  • The new warning test only asserts that a warning exists and mentions the extension path, but it doesn’t verify that the reason is included (the primary behavior change of this PR). Adding an assertion that the message starts with the localized resource prefix and has additional content helps prevent regressions without hardcoding an exception message.
        var message = messages.Single();
        Assert.AreEqual(TestMessageLevel.Warning, message.Level);
        Assert.Contains(extension, message.Message);
    }

The warning was deduplicated on UnloadableFiles, which is static and never
cleared. In design mode Visual Studio keeps vstest.console alive across many
discovery and run requests, so the user was told about a missing extension on
the first request and then never again. The same set also skipped the file for
the rest of the process, so an extension whose missing dependency has since
appeared stayed skipped until the process exits.

Split the two concerns. UnloadableFiles still answers whether loading the file
is worth another try, ReportedFiles answers whether the user has already seen
the warning, and ClearExtensions empties both. The runner calls that before
every discovery and run request, so the warning is once per run.

Reporting no longer depends on being the first to hit the failed load, the
skip path hands the file to the reporting too and the reporting decides.

Partial type loads stay quiet, only an assembly that yields no type at all is
reported, see microsoft#290.

🤖

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • src/Microsoft.TestPlatform.Common/Resources/Resources.Designer.cs: Generated file

/// dependency has since appeared is tried again on the next request instead of staying broken for the rest
/// of the process.
/// </summary>
private static readonly ConcurrentDictionary<string, string> UnloadableFiles = new();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants