Fixed line endings.
This commit is contained in:
Родитель
d8d284b0b4
Коммит
3f18aaa011
|
@ -0,0 +1,42 @@
|
|||
[patterns]
|
||||
|
||||
# Non human-editable files are binary
|
||||
|
||||
**.dsp = BIN
|
||||
**.dsw = BIN
|
||||
**.mk = BIN
|
||||
**.sln = BIN
|
||||
**.vcproj = BIN
|
||||
**.vsprops = BIN
|
||||
|
||||
**.aif = BIN
|
||||
**.aifc = BIN
|
||||
**.aiff = BIN
|
||||
**.au = BIN
|
||||
**.bmp = BIN
|
||||
**.db = BIN
|
||||
**.exe = BIN
|
||||
**.icns = BIN
|
||||
**.gif = BIN
|
||||
**.ico = BIN
|
||||
**.info = BIN
|
||||
**.jpg = BIN
|
||||
**.pck = BIN
|
||||
**.png = BIN
|
||||
**.psd = BIN
|
||||
**.tar = BIN
|
||||
**.wav = BIN
|
||||
**.whl = BIN
|
||||
**.xar = BIN
|
||||
**.zip = BIN
|
||||
|
||||
# Windows batch files work best with CRLF, there can be subtle problems with LF
|
||||
**.bat = CRLF
|
||||
|
||||
# All other files (which presumably are human-editable) are "native".
|
||||
# This must be the last rule!
|
||||
|
||||
** = native
|
||||
|
||||
[repository]
|
||||
native = LF
|
|
@ -1,203 +1,203 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.0
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudioTools.VSTestHost.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to restart Visual Studio because there is no active test run..
|
||||
/// </summary>
|
||||
internal static string CannotReconnectNoRunContext {
|
||||
get {
|
||||
return ResourceManager.GetString("CannotReconnectNoRunContext", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to attach debugger to Visual Studio process..
|
||||
/// </summary>
|
||||
internal static string FailedToAttach {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToAttach", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to connect to Visual Studio instance. Ensure VSTestHost is installed..
|
||||
/// </summary>
|
||||
internal static string FailedToConnect {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToConnect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to resume test run..
|
||||
/// </summary>
|
||||
internal static string FailedToResume {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToResume", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to determine bounds for screen capture..
|
||||
/// </summary>
|
||||
internal static string InvalidScreenBounds {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidScreenBounds", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.0
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudioTools.VSTestHost.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to restart Visual Studio because there is no active test run..
|
||||
/// </summary>
|
||||
internal static string CannotReconnectNoRunContext {
|
||||
get {
|
||||
return ResourceManager.GetString("CannotReconnectNoRunContext", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to attach debugger to Visual Studio process..
|
||||
/// </summary>
|
||||
internal static string FailedToAttach {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToAttach", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to connect to Visual Studio instance. Ensure VSTestHost is installed..
|
||||
/// </summary>
|
||||
internal static string FailedToConnect {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToConnect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to resume test run..
|
||||
/// </summary>
|
||||
internal static string FailedToResume {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToResume", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to determine bounds for screen capture..
|
||||
/// </summary>
|
||||
internal static string InvalidScreenBounds {
|
||||
get {
|
||||
return ResourceManager.GetString("InvalidScreenBounds", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Some required configuration values were missing.
|
||||
/// VSApplication: '{0}'
|
||||
/// VSExecutable: '{1}'
|
||||
/// VSVersion: '{2}'
|
||||
/// VSHive: '{3}'.
|
||||
/// </summary>
|
||||
internal static string MissingConfigurationValues {
|
||||
get {
|
||||
return ResourceManager.GetString("MissingConfigurationValues", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Visual Studio is no longer running..
|
||||
/// </summary>
|
||||
internal static string NoClient {
|
||||
get {
|
||||
return ResourceManager.GetString("NoClient", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There is no active test run..
|
||||
/// </summary>
|
||||
internal static string NoRunContext {
|
||||
get {
|
||||
return ResourceManager.GetString("NoRunContext", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No service provider is available in the current context..
|
||||
/// </summary>
|
||||
internal static string NoServiceProvider {
|
||||
get {
|
||||
return ResourceManager.GetString("NoServiceProvider", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to communicate with Visual Studio process during {0}: {1}.
|
||||
/// </summary>
|
||||
internal static string RemotingError {
|
||||
get {
|
||||
return ResourceManager.GetString("RemotingError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VSHive: '{3}'.
|
||||
/// </summary>
|
||||
internal static string MissingConfigurationValues {
|
||||
get {
|
||||
return ResourceManager.GetString("MissingConfigurationValues", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Visual Studio is no longer running..
|
||||
/// </summary>
|
||||
internal static string NoClient {
|
||||
get {
|
||||
return ResourceManager.GetString("NoClient", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There is no active test run..
|
||||
/// </summary>
|
||||
internal static string NoRunContext {
|
||||
get {
|
||||
return ResourceManager.GetString("NoRunContext", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No service provider is available in the current context..
|
||||
/// </summary>
|
||||
internal static string NoServiceProvider {
|
||||
get {
|
||||
return ResourceManager.GetString("NoServiceProvider", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to communicate with Visual Studio process during {0}: {1}.
|
||||
/// </summary>
|
||||
internal static string RemotingError {
|
||||
get {
|
||||
return ResourceManager.GetString("RemotingError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to communicate with Visual Studio process during {0}: {1}
|
||||
///{2}.
|
||||
/// </summary>
|
||||
internal static string RemotingErrorDebug {
|
||||
get {
|
||||
return ResourceManager.GetString("RemotingErrorDebug", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Retrying call into Visual Studio process from {0}..
|
||||
/// </summary>
|
||||
internal static string RetryRemoteCall {
|
||||
get {
|
||||
return ResourceManager.GetString("RetryRemoteCall", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Attempted to set service provider multiple time..
|
||||
/// </summary>
|
||||
internal static string ServiceProviderAlreadySet {
|
||||
get {
|
||||
return ResourceManager.GetString("ServiceProviderAlreadySet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to launch {1} ({0}, {2}{3}).
|
||||
/// </summary>
|
||||
internal static string VSFailedToLaunch {
|
||||
get {
|
||||
return ResourceManager.GetString("VSFailedToLaunch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Visual Studio failed to start within {0} seconds..
|
||||
/// </summary>
|
||||
internal static string VSLaunchTimeout {
|
||||
get {
|
||||
return ResourceManager.GetString("VSLaunchTimeout", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
///{2}.
|
||||
/// </summary>
|
||||
internal static string RemotingErrorDebug {
|
||||
get {
|
||||
return ResourceManager.GetString("RemotingErrorDebug", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Retrying call into Visual Studio process from {0}..
|
||||
/// </summary>
|
||||
internal static string RetryRemoteCall {
|
||||
get {
|
||||
return ResourceManager.GetString("RetryRemoteCall", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Attempted to set service provider multiple time..
|
||||
/// </summary>
|
||||
internal static string ServiceProviderAlreadySet {
|
||||
get {
|
||||
return ResourceManager.GetString("ServiceProviderAlreadySet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to launch {1} ({0}, {2}{3}).
|
||||
/// </summary>
|
||||
internal static string VSFailedToLaunch {
|
||||
get {
|
||||
return ResourceManager.GetString("VSFailedToLaunch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Visual Studio failed to start within {0} seconds..
|
||||
/// </summary>
|
||||
internal static string VSLaunchTimeout {
|
||||
get {
|
||||
return ResourceManager.GetString("VSLaunchTimeout", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,171 +1,171 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CannotReconnectNoRunContext" xml:space="preserve">
|
||||
<value>Unable to restart Visual Studio because there is no active test run.</value>
|
||||
</data>
|
||||
<data name="FailedToAttach" xml:space="preserve">
|
||||
<value>Unable to attach debugger to Visual Studio process.</value>
|
||||
</data>
|
||||
<data name="FailedToConnect" xml:space="preserve">
|
||||
<value>Failed to connect to Visual Studio instance. Ensure VSTestHost is installed.</value>
|
||||
</data>
|
||||
<data name="FailedToResume" xml:space="preserve">
|
||||
<value>Unable to resume test run.</value>
|
||||
</data>
|
||||
<data name="InvalidScreenBounds" xml:space="preserve">
|
||||
<value>Unable to determine bounds for screen capture.</value>
|
||||
</data>
|
||||
<data name="MissingConfigurationValues" xml:space="preserve">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CannotReconnectNoRunContext" xml:space="preserve">
|
||||
<value>Unable to restart Visual Studio because there is no active test run.</value>
|
||||
</data>
|
||||
<data name="FailedToAttach" xml:space="preserve">
|
||||
<value>Unable to attach debugger to Visual Studio process.</value>
|
||||
</data>
|
||||
<data name="FailedToConnect" xml:space="preserve">
|
||||
<value>Failed to connect to Visual Studio instance. Ensure VSTestHost is installed.</value>
|
||||
</data>
|
||||
<data name="FailedToResume" xml:space="preserve">
|
||||
<value>Unable to resume test run.</value>
|
||||
</data>
|
||||
<data name="InvalidScreenBounds" xml:space="preserve">
|
||||
<value>Unable to determine bounds for screen capture.</value>
|
||||
</data>
|
||||
<data name="MissingConfigurationValues" xml:space="preserve">
|
||||
<value>Some required configuration values were missing.
|
||||
VSApplication: '{0}'
|
||||
VSExecutable: '{1}'
|
||||
VSVersion: '{2}'
|
||||
VSHive: '{3}'</value>
|
||||
</data>
|
||||
<data name="NoClient" xml:space="preserve">
|
||||
<value>Visual Studio is no longer running.</value>
|
||||
</data>
|
||||
<data name="NoRunContext" xml:space="preserve">
|
||||
<value>There is no active test run.</value>
|
||||
</data>
|
||||
<data name="NoServiceProvider" xml:space="preserve">
|
||||
<value>No service provider is available in the current context.</value>
|
||||
</data>
|
||||
<data name="RemotingError" xml:space="preserve">
|
||||
<value>Failed to communicate with Visual Studio process during {0}: {1}</value>
|
||||
</data>
|
||||
<data name="RemotingErrorDebug" xml:space="preserve">
|
||||
VSHive: '{3}'</value>
|
||||
</data>
|
||||
<data name="NoClient" xml:space="preserve">
|
||||
<value>Visual Studio is no longer running.</value>
|
||||
</data>
|
||||
<data name="NoRunContext" xml:space="preserve">
|
||||
<value>There is no active test run.</value>
|
||||
</data>
|
||||
<data name="NoServiceProvider" xml:space="preserve">
|
||||
<value>No service provider is available in the current context.</value>
|
||||
</data>
|
||||
<data name="RemotingError" xml:space="preserve">
|
||||
<value>Failed to communicate with Visual Studio process during {0}: {1}</value>
|
||||
</data>
|
||||
<data name="RemotingErrorDebug" xml:space="preserve">
|
||||
<value>Failed to communicate with Visual Studio process during {0}: {1}
|
||||
{2}</value>
|
||||
</data>
|
||||
<data name="RetryRemoteCall" xml:space="preserve">
|
||||
<value>Retrying call into Visual Studio process from {0}.</value>
|
||||
</data>
|
||||
<data name="ServiceProviderAlreadySet" xml:space="preserve">
|
||||
<value>Attempted to set service provider multiple time.</value>
|
||||
</data>
|
||||
<data name="VSFailedToLaunch" xml:space="preserve">
|
||||
<value>Failed to launch {1} ({0}, {2}{3})</value>
|
||||
<comment>application, executable, version, hive</comment>
|
||||
</data>
|
||||
<data name="VSLaunchTimeout" xml:space="preserve">
|
||||
<value>Visual Studio failed to start within {0} seconds.</value>
|
||||
</data>
|
||||
{2}</value>
|
||||
</data>
|
||||
<data name="RetryRemoteCall" xml:space="preserve">
|
||||
<value>Retrying call into Visual Studio process from {0}.</value>
|
||||
</data>
|
||||
<data name="ServiceProviderAlreadySet" xml:space="preserve">
|
||||
<value>Attempted to set service provider multiple time.</value>
|
||||
</data>
|
||||
<data name="VSFailedToLaunch" xml:space="preserve">
|
||||
<value>Failed to launch {1} ({0}, {2}{3})</value>
|
||||
<comment>application, executable, version, hive</comment>
|
||||
</data>
|
||||
<data name="VSLaunchTimeout" xml:space="preserve">
|
||||
<value>Visual Studio failed to start within {0} seconds.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -1,55 +1,55 @@
|
|||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.Common;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
class TestProperties {
|
||||
private readonly ITestElement _testElement;
|
||||
private readonly Dictionary<string, string> _testSettings;
|
||||
|
||||
public TestProperties(ITestElement testElement, TestRunConfiguration runConfig) {
|
||||
_testElement = testElement;
|
||||
if (runConfig != null) {
|
||||
_testSettings = runConfig.TestSettingsProperties;
|
||||
}
|
||||
}
|
||||
|
||||
public string this[string key] {
|
||||
get {
|
||||
string value;
|
||||
TryGetValue(key, out value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out string value) {
|
||||
value = null;
|
||||
if (_testElement != null && _testElement.Properties.ContainsKey(key)) {
|
||||
try {
|
||||
value = (string)_testElement.Properties[key];
|
||||
return true;
|
||||
} catch (KeyNotFoundException) {
|
||||
} catch (InvalidCastException) {
|
||||
}
|
||||
}
|
||||
if (_testSettings != null) {
|
||||
return _testSettings.TryGetValue(key, out value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.Common;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
class TestProperties {
|
||||
private readonly ITestElement _testElement;
|
||||
private readonly Dictionary<string, string> _testSettings;
|
||||
|
||||
public TestProperties(ITestElement testElement, TestRunConfiguration runConfig) {
|
||||
_testElement = testElement;
|
||||
if (runConfig != null) {
|
||||
_testSettings = runConfig.TestSettingsProperties;
|
||||
}
|
||||
}
|
||||
|
||||
public string this[string key] {
|
||||
get {
|
||||
string value;
|
||||
TryGetValue(key, out value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out string value) {
|
||||
value = null;
|
||||
if (_testElement != null && _testElement.Properties.ContainsKey(key)) {
|
||||
try {
|
||||
value = (string)_testElement.Properties[key];
|
||||
return true;
|
||||
} catch (KeyNotFoundException) {
|
||||
} catch (InvalidCastException) {
|
||||
}
|
||||
}
|
||||
if (_testSettings != null) {
|
||||
return _testSettings.TryGetValue(key, out value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,214 +1,214 @@
|
|||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting;
|
||||
using Microsoft.VisualStudio.TestTools.Common;
|
||||
using Microsoft.VisualStudio.TestTools.Execution;
|
||||
using Microsoft.VisualStudio.TestTools.TestAdapter;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
/// <summary>
|
||||
/// Acts as a communication proxy between VS instances.
|
||||
///
|
||||
/// This class is instantiated in the client VS.
|
||||
/// </summary>
|
||||
class TesteeTestAdapter : MarshalByRefObject, ITestAdapter, IDisposable {
|
||||
private IRunContext _runContext;
|
||||
private readonly Dictionary<string, ITestAdapter> _adapters = new Dictionary<string, ITestAdapter>();
|
||||
|
||||
public const string Url = "vstest";
|
||||
|
||||
public TesteeTestAdapter() { }
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
RemotingServices.Disconnect(this);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInitialized {
|
||||
get {
|
||||
return _runContext != null;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ITestAdapter> GetAdapters() {
|
||||
lock (_adapters) {
|
||||
return _adapters.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private ITestAdapter GetAdapter(string adapterName) {
|
||||
ITestAdapter adapter;
|
||||
lock (_adapters) {
|
||||
if (_adapters.TryGetValue(adapterName, out adapter)) {
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
||||
adapter = (ITestAdapter)Activator.CreateInstance(Type.GetType(adapterName));
|
||||
if (_runContext != null) {
|
||||
adapter.Initialize(_runContext);
|
||||
}
|
||||
|
||||
lock (_adapters) {
|
||||
try {
|
||||
_adapters.Add(adapterName, adapter);
|
||||
} catch (ArgumentException) {
|
||||
adapter.Cleanup();
|
||||
adapter = _adapters[adapterName];
|
||||
}
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
|
||||
public void Initialize(IRunContext runContext) {
|
||||
_runContext = runContext;
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.Initialize(runContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void PreTestRunFinished(IRunContext runContext) {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.PreTestRunFinished(runContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveMessage(object message) {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.ReceiveMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void AbortTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.AbortTestRun();
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup() {
|
||||
_runContext = null;
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.Cleanup();
|
||||
}
|
||||
_adapters.Clear();
|
||||
}
|
||||
|
||||
public void PauseTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.PauseTestRun();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResumeTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.ResumeTestRun();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetProperty(
|
||||
ITestElement testElement,
|
||||
IRunContext runContext,
|
||||
string propertyName,
|
||||
out string value
|
||||
) {
|
||||
value = null;
|
||||
|
||||
var runProps = runContext.RunConfig.TestRun.RunConfiguration.TestSettingsProperties;
|
||||
|
||||
if (testElement.Properties.ContainsKey(propertyName)) {
|
||||
value = testElement.Properties[propertyName] as string;
|
||||
return value != null;
|
||||
}
|
||||
return runProps.TryGetValue(propertyName, out value);
|
||||
}
|
||||
|
||||
public void Run(ITestElement testElement, ITestContext testContext) {
|
||||
var testAdapter = GetAdapter(testElement.Adapter);
|
||||
|
||||
using (var screenRecorder = StartSceenRecorder(testElement, _runContext)) {
|
||||
try {
|
||||
testAdapter.Run(testElement, testContext);
|
||||
} catch (Exception ex) {
|
||||
var message = new TextTestResultMessage(
|
||||
_runContext.RunConfig.TestRun.Id,
|
||||
testElement,
|
||||
ex.ToString()
|
||||
);
|
||||
testContext.ResultSink.AddResult(message);
|
||||
}
|
||||
|
||||
if (screenRecorder != null && !string.IsNullOrEmpty(screenRecorder.Failure)) {
|
||||
var message = new TextTestResultMessage(
|
||||
_runContext.RunConfig.TestRun.Id,
|
||||
testElement,
|
||||
screenRecorder.Failure
|
||||
);
|
||||
testContext.ResultSink.AddResult(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ScreenRecorder StartSceenRecorder(ITestElement testElement, IRunContext runContext) {
|
||||
string screenCapture;
|
||||
|
||||
if (!TryGetProperty(testElement, _runContext, VSTestProperties.ScreenCapture.Key, out screenCapture) ||
|
||||
string.IsNullOrEmpty(screenCapture)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (!Path.IsPathRooted(screenCapture)) {
|
||||
screenCapture = Path.Combine(
|
||||
_runContext.RunConfig.TestRun.RunConfiguration.RunDeploymentOutDirectory,
|
||||
screenCapture
|
||||
);
|
||||
}
|
||||
} catch (ArgumentException) {
|
||||
return null;
|
||||
}
|
||||
|
||||
screenCapture = screenCapture.Replace("$id$", testElement.HumanReadableId);
|
||||
screenCapture = screenCapture.Replace("$date$", DateTime.Today.ToShortDateString());
|
||||
var screenRecorder = new ScreenRecorder(screenCapture);
|
||||
|
||||
string intervalString;
|
||||
int interval;
|
||||
if (TryGetProperty(testElement, runContext, VSTestProperties.ScreenCapture.IntervalKey, out intervalString) &&
|
||||
int.TryParse(intervalString, out interval)) {
|
||||
screenRecorder.Interval = TimeSpan.FromMilliseconds(interval);
|
||||
} else {
|
||||
screenRecorder.Interval = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
return screenRecorder;
|
||||
}
|
||||
|
||||
public void StopTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.StopTestRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting;
|
||||
using Microsoft.VisualStudio.TestTools.Common;
|
||||
using Microsoft.VisualStudio.TestTools.Execution;
|
||||
using Microsoft.VisualStudio.TestTools.TestAdapter;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
/// <summary>
|
||||
/// Acts as a communication proxy between VS instances.
|
||||
///
|
||||
/// This class is instantiated in the client VS.
|
||||
/// </summary>
|
||||
class TesteeTestAdapter : MarshalByRefObject, ITestAdapter, IDisposable {
|
||||
private IRunContext _runContext;
|
||||
private readonly Dictionary<string, ITestAdapter> _adapters = new Dictionary<string, ITestAdapter>();
|
||||
|
||||
public const string Url = "vstest";
|
||||
|
||||
public TesteeTestAdapter() { }
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
RemotingServices.Disconnect(this);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInitialized {
|
||||
get {
|
||||
return _runContext != null;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ITestAdapter> GetAdapters() {
|
||||
lock (_adapters) {
|
||||
return _adapters.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private ITestAdapter GetAdapter(string adapterName) {
|
||||
ITestAdapter adapter;
|
||||
lock (_adapters) {
|
||||
if (_adapters.TryGetValue(adapterName, out adapter)) {
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
|
||||
adapter = (ITestAdapter)Activator.CreateInstance(Type.GetType(adapterName));
|
||||
if (_runContext != null) {
|
||||
adapter.Initialize(_runContext);
|
||||
}
|
||||
|
||||
lock (_adapters) {
|
||||
try {
|
||||
_adapters.Add(adapterName, adapter);
|
||||
} catch (ArgumentException) {
|
||||
adapter.Cleanup();
|
||||
adapter = _adapters[adapterName];
|
||||
}
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
|
||||
public void Initialize(IRunContext runContext) {
|
||||
_runContext = runContext;
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.Initialize(runContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void PreTestRunFinished(IRunContext runContext) {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.PreTestRunFinished(runContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveMessage(object message) {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.ReceiveMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void AbortTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.AbortTestRun();
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup() {
|
||||
_runContext = null;
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.Cleanup();
|
||||
}
|
||||
_adapters.Clear();
|
||||
}
|
||||
|
||||
public void PauseTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.PauseTestRun();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResumeTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.ResumeTestRun();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetProperty(
|
||||
ITestElement testElement,
|
||||
IRunContext runContext,
|
||||
string propertyName,
|
||||
out string value
|
||||
) {
|
||||
value = null;
|
||||
|
||||
var runProps = runContext.RunConfig.TestRun.RunConfiguration.TestSettingsProperties;
|
||||
|
||||
if (testElement.Properties.ContainsKey(propertyName)) {
|
||||
value = testElement.Properties[propertyName] as string;
|
||||
return value != null;
|
||||
}
|
||||
return runProps.TryGetValue(propertyName, out value);
|
||||
}
|
||||
|
||||
public void Run(ITestElement testElement, ITestContext testContext) {
|
||||
var testAdapter = GetAdapter(testElement.Adapter);
|
||||
|
||||
using (var screenRecorder = StartSceenRecorder(testElement, _runContext)) {
|
||||
try {
|
||||
testAdapter.Run(testElement, testContext);
|
||||
} catch (Exception ex) {
|
||||
var message = new TextTestResultMessage(
|
||||
_runContext.RunConfig.TestRun.Id,
|
||||
testElement,
|
||||
ex.ToString()
|
||||
);
|
||||
testContext.ResultSink.AddResult(message);
|
||||
}
|
||||
|
||||
if (screenRecorder != null && !string.IsNullOrEmpty(screenRecorder.Failure)) {
|
||||
var message = new TextTestResultMessage(
|
||||
_runContext.RunConfig.TestRun.Id,
|
||||
testElement,
|
||||
screenRecorder.Failure
|
||||
);
|
||||
testContext.ResultSink.AddResult(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ScreenRecorder StartSceenRecorder(ITestElement testElement, IRunContext runContext) {
|
||||
string screenCapture;
|
||||
|
||||
if (!TryGetProperty(testElement, _runContext, VSTestProperties.ScreenCapture.Key, out screenCapture) ||
|
||||
string.IsNullOrEmpty(screenCapture)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (!Path.IsPathRooted(screenCapture)) {
|
||||
screenCapture = Path.Combine(
|
||||
_runContext.RunConfig.TestRun.RunConfiguration.RunDeploymentOutDirectory,
|
||||
screenCapture
|
||||
);
|
||||
}
|
||||
} catch (ArgumentException) {
|
||||
return null;
|
||||
}
|
||||
|
||||
screenCapture = screenCapture.Replace("$id$", testElement.HumanReadableId);
|
||||
screenCapture = screenCapture.Replace("$date$", DateTime.Today.ToShortDateString());
|
||||
var screenRecorder = new ScreenRecorder(screenCapture);
|
||||
|
||||
string intervalString;
|
||||
int interval;
|
||||
if (TryGetProperty(testElement, runContext, VSTestProperties.ScreenCapture.IntervalKey, out intervalString) &&
|
||||
int.TryParse(intervalString, out interval)) {
|
||||
screenRecorder.Interval = TimeSpan.FromMilliseconds(interval);
|
||||
} else {
|
||||
screenRecorder.Interval = TimeSpan.FromSeconds(1);
|
||||
}
|
||||
|
||||
return screenRecorder;
|
||||
}
|
||||
|
||||
public void StopTestRun() {
|
||||
foreach (var adapter in GetAdapters()) {
|
||||
adapter.StopTestRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,424 +1,424 @@
|
|||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Remoting;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.Common;
|
||||
using Microsoft.VisualStudio.TestTools.Execution;
|
||||
using Microsoft.VisualStudio.TestTools.TestAdapter;
|
||||
using Microsoft.VisualStudioTools.VSTestHost.Internal;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost {
|
||||
/// <summary>
|
||||
/// Executes tests attributed with HostType("VSTestHost").
|
||||
///
|
||||
/// This class is instantiated by the EXECUTION ENGINE and communicates with
|
||||
/// the TESTEE via <see cref="TesteeTestAdapter"/> over IPC.
|
||||
/// </summary>
|
||||
class TesterTestAdapter : ITestAdapter {
|
||||
private Internal.VisualStudio _ide;
|
||||
private string _currentApplication, _currentExecutable, _currentHive;
|
||||
private Version _currentVersion;
|
||||
|
||||
private Guid _runId;
|
||||
private IRunContext _runContext;
|
||||
private TesteeTestAdapter _remote;
|
||||
private bool _mockVs;
|
||||
|
||||
public TesterTestAdapter() { }
|
||||
|
||||
private async Task Connect(
|
||||
string application,
|
||||
string executable,
|
||||
Version version,
|
||||
string hive,
|
||||
CancellationToken cancel
|
||||
) {
|
||||
if (_ide != null &&
|
||||
_remote != null &&
|
||||
application == _currentApplication &&
|
||||
executable == _currentExecutable &&
|
||||
version == _currentVersion &&
|
||||
hive == _currentHive) {
|
||||
return;
|
||||
}
|
||||
|
||||
Close();
|
||||
|
||||
Internal.VisualStudio ide = null;
|
||||
try {
|
||||
ide = await Internal.VisualStudio.LaunchAsync(application, executable, version, hive, cancel);
|
||||
|
||||
var p = Process.GetProcessById(ide.ProcessId);
|
||||
var url = string.Format("ipc://{0}/{1}", VSTestHostPackage.GetChannelName(p), TesteeTestAdapter.Url);
|
||||
|
||||
try {
|
||||
_remote = (TesteeTestAdapter)RemotingServices.Connect(typeof(TesteeTestAdapter), url);
|
||||
} catch (RemotingException ex) {
|
||||
throw new InvalidOperationException(Resources.FailedToConnect, ex);
|
||||
}
|
||||
|
||||
_currentApplication = application;
|
||||
_currentExecutable = executable;
|
||||
_currentVersion = version;
|
||||
_currentHive = hive;
|
||||
_ide = ide;
|
||||
ide = null;
|
||||
} finally {
|
||||
if (ide != null) {
|
||||
ide.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes VS and clears our state. Close may be called multiple times
|
||||
/// safely, and may be safely followed by another call to Connect with
|
||||
/// the same or different parameters.
|
||||
/// </summary>
|
||||
private void Close() {
|
||||
var ide = Interlocked.Exchange(ref _ide, null);
|
||||
var remote = Interlocked.Exchange(ref _remote, null);
|
||||
|
||||
var disposableRemote = remote as IDisposable;
|
||||
if (disposableRemote != null) {
|
||||
try {
|
||||
disposableRemote.Dispose();
|
||||
} catch (RemotingException) {
|
||||
}
|
||||
}
|
||||
if (ide != null) {
|
||||
try {
|
||||
// Try to close VS gracefully, otherwise Dispose() will just
|
||||
// go in and kill it
|
||||
ide.DTE.Quit();
|
||||
} catch (Exception ex) {
|
||||
Trace.TraceError(ex.ToString());
|
||||
}
|
||||
ide.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsClientAlive() {
|
||||
var ide = _ide;
|
||||
var remote = _remote;
|
||||
|
||||
if (ide == null || remote == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (remote.IsInitialized) {
|
||||
return true;
|
||||
}
|
||||
} catch (RemotingException) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements initialization. This is called by ITestAdapter.Initialize
|
||||
/// (a synchronous function) which will block until this function is
|
||||
/// completed.
|
||||
/// </summary>
|
||||
/// <param name="runContext">
|
||||
/// The context for the current test run.
|
||||
/// </param>
|
||||
private async Task InitializeWorker(IRunContext runContext, ITestElement testElement) {
|
||||
string application, executable, versionString, hive;
|
||||
Version version;
|
||||
string launchTimeoutInSecondsString;
|
||||
int launchTimeoutInSeconds;
|
||||
|
||||
|
||||
var vars = new TestProperties(testElement, _runContext.RunConfig.TestRun.RunConfiguration);
|
||||
|
||||
// VSApplication is the registry key name like 'VisualStudio'
|
||||
application = vars[VSTestProperties.VSApplication.Key] ?? VSTestProperties.VSApplication.VisualStudio;
|
||||
// VSExecutableName is the executable name like 'devenv'
|
||||
if (!vars.TryGetValue(VSTestProperties.VSExecutable.Key, out executable)) {
|
||||
executable = VSTestProperties.VSExecutable.DevEnv;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(executable) &&
|
||||
string.IsNullOrEmpty(Path.GetExtension(executable))) {
|
||||
executable = Path.ChangeExtension(executable, ".exe");
|
||||
}
|
||||
|
||||
// VSVersion is the version like '12.0'
|
||||
if (!vars.TryGetValue(VSTestProperties.VSVersion.Key, out versionString) ||
|
||||
!Version.TryParse(versionString, out version)) {
|
||||
version = new Version(int.Parse(AssemblyVersionInfo.VSVersion), 0);
|
||||
}
|
||||
|
||||
// VSHive is the optional hive like 'Exp'
|
||||
hive = vars[VSTestProperties.VSHive.Key] ?? VSTestProperties.VSHive.Exp;
|
||||
|
||||
if (!vars.TryGetValue(VSTestProperties.VSLaunchTimeoutInSeconds.Key, out launchTimeoutInSecondsString) ||
|
||||
!int.TryParse(launchTimeoutInSecondsString, out launchTimeoutInSeconds)) {
|
||||
launchTimeoutInSeconds = 30;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(application) || string.IsNullOrEmpty(executable) || version == null) {
|
||||
throw new ArgumentException(string.Format(
|
||||
Resources.MissingConfigurationValues,
|
||||
application ?? "(null)",
|
||||
executable ?? "(null)",
|
||||
version != null ? version.ToString() : "(null)",
|
||||
hive ?? "(null)"
|
||||
));
|
||||
}
|
||||
|
||||
_mockVs = (application == VSTestProperties.VSApplication.Mock);
|
||||
if (_mockVs) {
|
||||
_runContext = runContext;
|
||||
_remote = new TesteeTestAdapter();
|
||||
_remote.Initialize(_runContext);
|
||||
// In the mock case tester and testee are the same process, therefore
|
||||
// VSTestContext is in our process too. So we can just set this value
|
||||
// directly here.
|
||||
VSTestContext.IsMock = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Detect and perform first run of VS if necessary.
|
||||
// The first time a VS hive is run, the user sees a dialog allowing
|
||||
// them to select settings such as the theme. We can avoid this by
|
||||
// running devenv.exe /resetSettings <path to profile.settings>
|
||||
// first, though it is not trivial to detect when this is necessary.
|
||||
// Without having done this, all tests will time out. For now, the
|
||||
// user is responsible for running VS at least once before
|
||||
// attempting to execute tests.
|
||||
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(launchTimeoutInSeconds));
|
||||
try {
|
||||
await Connect(application, executable, version, hive, cts.Token);
|
||||
} catch (OperationCanceledException ex) {
|
||||
throw new TimeoutException(string.Format(Resources.VSLaunchTimeout, launchTimeoutInSeconds), ex);
|
||||
} catch (Exception ex) {
|
||||
throw new InvalidOperationException(
|
||||
string.Format(Resources.VSFailedToLaunch, application, executable, version, hive),
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static TestRunTextResultMessage GetFailure(Exception ex, Guid runId) {
|
||||
var res = new TestRunTextResultMessage(runId, ex.Message);
|
||||
#if DEBUG
|
||||
if (ex.InnerException != null) {
|
||||
res.SystemException = ex.InnerException;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
private bool InitializeForTest(ITestElement testElement, IRunContext runContext) {
|
||||
var runId = runContext.RunConfig.TestRun.Id;
|
||||
TestRunTextResultMessage failure = null;
|
||||
|
||||
try {
|
||||
InitializeWorker(runContext, testElement).GetAwaiter().GetResult();
|
||||
_remote.Initialize(_runContext);
|
||||
|
||||
AttachDebuggerIfNeeded(runContext, _ide);
|
||||
} catch (ArgumentException ex) {
|
||||
failure = GetFailure(ex, runId);
|
||||
} catch (TimeoutException ex) {
|
||||
failure = GetFailure(ex, runId);
|
||||
} catch (InvalidOperationException ex) {
|
||||
failure = GetFailure(ex, runId);
|
||||
} catch (Exception ex) {
|
||||
failure = new TestRunTextResultMessage(
|
||||
runId,
|
||||
string.Format("{0}: {1}{2}{3}", ex.GetType().Name, ex.Message, Environment.NewLine, ex)
|
||||
);
|
||||
failure.SystemException = ex;
|
||||
}
|
||||
|
||||
if (failure != null) {
|
||||
runContext.ResultSink.AddResult(failure);
|
||||
runContext.StopTestRun();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AttachDebuggerIfNeeded(IRunContext runContext, Internal.VisualStudio ide) {
|
||||
var config = runContext.RunConfig.TestRun.RunConfiguration;
|
||||
if (config.IsExecutedUnderDebugger && ide != null) {
|
||||
// If we're debugging, tell our host VS to attach to the new VS
|
||||
// instance we just started.
|
||||
bool mixedMode = false;
|
||||
string debugMixedMode;
|
||||
if (!config.TestSettingsProperties.TryGetValue(
|
||||
VSTestProperties.VSDebugMixedMode.Key,
|
||||
out debugMixedMode) ||
|
||||
!bool.TryParse(debugMixedMode, out mixedMode)
|
||||
) {
|
||||
mixedMode = false;
|
||||
}
|
||||
TesterDebugAttacherShared.AttachDebugger(ide.ProcessId, mixedMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendMessage(IRunContext runContext, string message, ITestElement currentTest = null) {
|
||||
if (runContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var runId = runContext.RunConfig.TestRun.Id;
|
||||
|
||||
TestMessage msg;
|
||||
if (currentTest == null) {
|
||||
msg = new TestRunTextResultMessage(runId, message);
|
||||
} else {
|
||||
msg = new TextTestResultMessage(runId, currentTest, message);
|
||||
}
|
||||
|
||||
runContext.ResultSink.AddResult(msg);
|
||||
}
|
||||
|
||||
private bool RemoteCall(
|
||||
Action<TesteeTestAdapter> action,
|
||||
int retries = 2,
|
||||
bool restartVS = true,
|
||||
ITestElement currentTest = null,
|
||||
[CallerMemberName] string caller = null
|
||||
) {
|
||||
var runContext = _runContext;
|
||||
if (runContext == null) {
|
||||
throw new InvalidOperationException(Resources.NoRunContext);
|
||||
}
|
||||
|
||||
if (currentTest != null) {
|
||||
InitializeForTest(currentTest, runContext);
|
||||
}
|
||||
|
||||
bool firstAttempt = true;
|
||||
while (retries-- > 0) {
|
||||
if (!_mockVs && !firstAttempt) {
|
||||
// Send a message announcing that we are retrying the call
|
||||
SendMessage(
|
||||
runContext,
|
||||
string.Format(
|
||||
Resources.RetryRemoteCall,
|
||||
currentTest != null ? currentTest.HumanReadableId : caller
|
||||
),
|
||||
currentTest
|
||||
);
|
||||
}
|
||||
firstAttempt = false;
|
||||
|
||||
if (!IsClientAlive()) {
|
||||
Close();
|
||||
|
||||
if (restartVS && currentTest != null) {
|
||||
SendMessage(runContext, "Restarting VS", currentTest);
|
||||
InitializeForTest(currentTest, runContext);
|
||||
} else {
|
||||
SendMessage(runContext, Resources.NoClient, currentTest);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var remote = _remote;
|
||||
if (remote == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
action(remote);
|
||||
return true;
|
||||
} catch (RemotingException ex) {
|
||||
#if DEBUG
|
||||
var msg = string.Format(Resources.RemotingErrorDebug, caller, ex.Message, ex.ToString());
|
||||
#else
|
||||
var msg = string.Format(Resources.RemotingError, caller, ex.Message);
|
||||
#endif
|
||||
SendMessage(runContext, msg, currentTest);
|
||||
|
||||
// Close _remote and let EnsureClient bring it back if
|
||||
// requested by the caller
|
||||
var disposableRemote = Interlocked.Exchange(ref _remote, null) as IDisposable;
|
||||
if (disposableRemote != null) {
|
||||
try {
|
||||
disposableRemote.Dispose();
|
||||
} catch (RemotingException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(Resources.NoClient);
|
||||
}
|
||||
|
||||
|
||||
#region ITestAdapter members
|
||||
|
||||
public void Initialize(IRunContext runContext) {
|
||||
_runContext = runContext;
|
||||
_runId = runContext.RunConfig.TestRun.Id;
|
||||
}
|
||||
|
||||
public void Cleanup() {
|
||||
RemoteCall(r => r.Cleanup());
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
public void PreTestRunFinished(IRunContext runContext) {
|
||||
RemoteCall(r => r.PreTestRunFinished(runContext));
|
||||
}
|
||||
|
||||
public void ReceiveMessage(object message) {
|
||||
RemoteCall(r => r.ReceiveMessage(message));
|
||||
}
|
||||
|
||||
|
||||
public void AbortTestRun() {
|
||||
RemoteCall(r => r.AbortTestRun(), restartVS: false);
|
||||
}
|
||||
|
||||
public void PauseTestRun() {
|
||||
RemoteCall(r => r.PauseTestRun(), restartVS: false);
|
||||
}
|
||||
|
||||
public void ResumeTestRun() {
|
||||
if (!RemoteCall(r => r.ResumeTestRun())) {
|
||||
throw new InvalidOperationException(string.Format(Resources.FailedToResume));
|
||||
}
|
||||
}
|
||||
|
||||
public void Run(ITestElement testElement, ITestContext testContext) {
|
||||
if (!RemoteCall(r => r.Run(testElement, testContext), currentTest: testElement)) {
|
||||
testContext.ResultSink.AddResult(new TestResult(".", _runId, testElement) {
|
||||
Outcome = TestOutcome.NotRunnable
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void StopTestRun() {
|
||||
RemoteCall(r => r.StopTestRun(), restartVS: false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Remoting;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.Common;
|
||||
using Microsoft.VisualStudio.TestTools.Execution;
|
||||
using Microsoft.VisualStudio.TestTools.TestAdapter;
|
||||
using Microsoft.VisualStudioTools.VSTestHost.Internal;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost {
|
||||
/// <summary>
|
||||
/// Executes tests attributed with HostType("VSTestHost").
|
||||
///
|
||||
/// This class is instantiated by the EXECUTION ENGINE and communicates with
|
||||
/// the TESTEE via <see cref="TesteeTestAdapter"/> over IPC.
|
||||
/// </summary>
|
||||
class TesterTestAdapter : ITestAdapter {
|
||||
private Internal.VisualStudio _ide;
|
||||
private string _currentApplication, _currentExecutable, _currentHive;
|
||||
private Version _currentVersion;
|
||||
|
||||
private Guid _runId;
|
||||
private IRunContext _runContext;
|
||||
private TesteeTestAdapter _remote;
|
||||
private bool _mockVs;
|
||||
|
||||
public TesterTestAdapter() { }
|
||||
|
||||
private async Task Connect(
|
||||
string application,
|
||||
string executable,
|
||||
Version version,
|
||||
string hive,
|
||||
CancellationToken cancel
|
||||
) {
|
||||
if (_ide != null &&
|
||||
_remote != null &&
|
||||
application == _currentApplication &&
|
||||
executable == _currentExecutable &&
|
||||
version == _currentVersion &&
|
||||
hive == _currentHive) {
|
||||
return;
|
||||
}
|
||||
|
||||
Close();
|
||||
|
||||
Internal.VisualStudio ide = null;
|
||||
try {
|
||||
ide = await Internal.VisualStudio.LaunchAsync(application, executable, version, hive, cancel);
|
||||
|
||||
var p = Process.GetProcessById(ide.ProcessId);
|
||||
var url = string.Format("ipc://{0}/{1}", VSTestHostPackage.GetChannelName(p), TesteeTestAdapter.Url);
|
||||
|
||||
try {
|
||||
_remote = (TesteeTestAdapter)RemotingServices.Connect(typeof(TesteeTestAdapter), url);
|
||||
} catch (RemotingException ex) {
|
||||
throw new InvalidOperationException(Resources.FailedToConnect, ex);
|
||||
}
|
||||
|
||||
_currentApplication = application;
|
||||
_currentExecutable = executable;
|
||||
_currentVersion = version;
|
||||
_currentHive = hive;
|
||||
_ide = ide;
|
||||
ide = null;
|
||||
} finally {
|
||||
if (ide != null) {
|
||||
ide.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes VS and clears our state. Close may be called multiple times
|
||||
/// safely, and may be safely followed by another call to Connect with
|
||||
/// the same or different parameters.
|
||||
/// </summary>
|
||||
private void Close() {
|
||||
var ide = Interlocked.Exchange(ref _ide, null);
|
||||
var remote = Interlocked.Exchange(ref _remote, null);
|
||||
|
||||
var disposableRemote = remote as IDisposable;
|
||||
if (disposableRemote != null) {
|
||||
try {
|
||||
disposableRemote.Dispose();
|
||||
} catch (RemotingException) {
|
||||
}
|
||||
}
|
||||
if (ide != null) {
|
||||
try {
|
||||
// Try to close VS gracefully, otherwise Dispose() will just
|
||||
// go in and kill it
|
||||
ide.DTE.Quit();
|
||||
} catch (Exception ex) {
|
||||
Trace.TraceError(ex.ToString());
|
||||
}
|
||||
ide.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsClientAlive() {
|
||||
var ide = _ide;
|
||||
var remote = _remote;
|
||||
|
||||
if (ide == null || remote == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (remote.IsInitialized) {
|
||||
return true;
|
||||
}
|
||||
} catch (RemotingException) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements initialization. This is called by ITestAdapter.Initialize
|
||||
/// (a synchronous function) which will block until this function is
|
||||
/// completed.
|
||||
/// </summary>
|
||||
/// <param name="runContext">
|
||||
/// The context for the current test run.
|
||||
/// </param>
|
||||
private async Task InitializeWorker(IRunContext runContext, ITestElement testElement) {
|
||||
string application, executable, versionString, hive;
|
||||
Version version;
|
||||
string launchTimeoutInSecondsString;
|
||||
int launchTimeoutInSeconds;
|
||||
|
||||
|
||||
var vars = new TestProperties(testElement, _runContext.RunConfig.TestRun.RunConfiguration);
|
||||
|
||||
// VSApplication is the registry key name like 'VisualStudio'
|
||||
application = vars[VSTestProperties.VSApplication.Key] ?? VSTestProperties.VSApplication.VisualStudio;
|
||||
// VSExecutableName is the executable name like 'devenv'
|
||||
if (!vars.TryGetValue(VSTestProperties.VSExecutable.Key, out executable)) {
|
||||
executable = VSTestProperties.VSExecutable.DevEnv;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(executable) &&
|
||||
string.IsNullOrEmpty(Path.GetExtension(executable))) {
|
||||
executable = Path.ChangeExtension(executable, ".exe");
|
||||
}
|
||||
|
||||
// VSVersion is the version like '12.0'
|
||||
if (!vars.TryGetValue(VSTestProperties.VSVersion.Key, out versionString) ||
|
||||
!Version.TryParse(versionString, out version)) {
|
||||
version = new Version(int.Parse(AssemblyVersionInfo.VSVersion), 0);
|
||||
}
|
||||
|
||||
// VSHive is the optional hive like 'Exp'
|
||||
hive = vars[VSTestProperties.VSHive.Key] ?? VSTestProperties.VSHive.Exp;
|
||||
|
||||
if (!vars.TryGetValue(VSTestProperties.VSLaunchTimeoutInSeconds.Key, out launchTimeoutInSecondsString) ||
|
||||
!int.TryParse(launchTimeoutInSecondsString, out launchTimeoutInSeconds)) {
|
||||
launchTimeoutInSeconds = 30;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(application) || string.IsNullOrEmpty(executable) || version == null) {
|
||||
throw new ArgumentException(string.Format(
|
||||
Resources.MissingConfigurationValues,
|
||||
application ?? "(null)",
|
||||
executable ?? "(null)",
|
||||
version != null ? version.ToString() : "(null)",
|
||||
hive ?? "(null)"
|
||||
));
|
||||
}
|
||||
|
||||
_mockVs = (application == VSTestProperties.VSApplication.Mock);
|
||||
if (_mockVs) {
|
||||
_runContext = runContext;
|
||||
_remote = new TesteeTestAdapter();
|
||||
_remote.Initialize(_runContext);
|
||||
// In the mock case tester and testee are the same process, therefore
|
||||
// VSTestContext is in our process too. So we can just set this value
|
||||
// directly here.
|
||||
VSTestContext.IsMock = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Detect and perform first run of VS if necessary.
|
||||
// The first time a VS hive is run, the user sees a dialog allowing
|
||||
// them to select settings such as the theme. We can avoid this by
|
||||
// running devenv.exe /resetSettings <path to profile.settings>
|
||||
// first, though it is not trivial to detect when this is necessary.
|
||||
// Without having done this, all tests will time out. For now, the
|
||||
// user is responsible for running VS at least once before
|
||||
// attempting to execute tests.
|
||||
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(launchTimeoutInSeconds));
|
||||
try {
|
||||
await Connect(application, executable, version, hive, cts.Token);
|
||||
} catch (OperationCanceledException ex) {
|
||||
throw new TimeoutException(string.Format(Resources.VSLaunchTimeout, launchTimeoutInSeconds), ex);
|
||||
} catch (Exception ex) {
|
||||
throw new InvalidOperationException(
|
||||
string.Format(Resources.VSFailedToLaunch, application, executable, version, hive),
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static TestRunTextResultMessage GetFailure(Exception ex, Guid runId) {
|
||||
var res = new TestRunTextResultMessage(runId, ex.Message);
|
||||
#if DEBUG
|
||||
if (ex.InnerException != null) {
|
||||
res.SystemException = ex.InnerException;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
private bool InitializeForTest(ITestElement testElement, IRunContext runContext) {
|
||||
var runId = runContext.RunConfig.TestRun.Id;
|
||||
TestRunTextResultMessage failure = null;
|
||||
|
||||
try {
|
||||
InitializeWorker(runContext, testElement).GetAwaiter().GetResult();
|
||||
_remote.Initialize(_runContext);
|
||||
|
||||
AttachDebuggerIfNeeded(runContext, _ide);
|
||||
} catch (ArgumentException ex) {
|
||||
failure = GetFailure(ex, runId);
|
||||
} catch (TimeoutException ex) {
|
||||
failure = GetFailure(ex, runId);
|
||||
} catch (InvalidOperationException ex) {
|
||||
failure = GetFailure(ex, runId);
|
||||
} catch (Exception ex) {
|
||||
failure = new TestRunTextResultMessage(
|
||||
runId,
|
||||
string.Format("{0}: {1}{2}{3}", ex.GetType().Name, ex.Message, Environment.NewLine, ex)
|
||||
);
|
||||
failure.SystemException = ex;
|
||||
}
|
||||
|
||||
if (failure != null) {
|
||||
runContext.ResultSink.AddResult(failure);
|
||||
runContext.StopTestRun();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AttachDebuggerIfNeeded(IRunContext runContext, Internal.VisualStudio ide) {
|
||||
var config = runContext.RunConfig.TestRun.RunConfiguration;
|
||||
if (config.IsExecutedUnderDebugger && ide != null) {
|
||||
// If we're debugging, tell our host VS to attach to the new VS
|
||||
// instance we just started.
|
||||
bool mixedMode = false;
|
||||
string debugMixedMode;
|
||||
if (!config.TestSettingsProperties.TryGetValue(
|
||||
VSTestProperties.VSDebugMixedMode.Key,
|
||||
out debugMixedMode) ||
|
||||
!bool.TryParse(debugMixedMode, out mixedMode)
|
||||
) {
|
||||
mixedMode = false;
|
||||
}
|
||||
TesterDebugAttacherShared.AttachDebugger(ide.ProcessId, mixedMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendMessage(IRunContext runContext, string message, ITestElement currentTest = null) {
|
||||
if (runContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var runId = runContext.RunConfig.TestRun.Id;
|
||||
|
||||
TestMessage msg;
|
||||
if (currentTest == null) {
|
||||
msg = new TestRunTextResultMessage(runId, message);
|
||||
} else {
|
||||
msg = new TextTestResultMessage(runId, currentTest, message);
|
||||
}
|
||||
|
||||
runContext.ResultSink.AddResult(msg);
|
||||
}
|
||||
|
||||
private bool RemoteCall(
|
||||
Action<TesteeTestAdapter> action,
|
||||
int retries = 2,
|
||||
bool restartVS = true,
|
||||
ITestElement currentTest = null,
|
||||
[CallerMemberName] string caller = null
|
||||
) {
|
||||
var runContext = _runContext;
|
||||
if (runContext == null) {
|
||||
throw new InvalidOperationException(Resources.NoRunContext);
|
||||
}
|
||||
|
||||
if (currentTest != null) {
|
||||
InitializeForTest(currentTest, runContext);
|
||||
}
|
||||
|
||||
bool firstAttempt = true;
|
||||
while (retries-- > 0) {
|
||||
if (!_mockVs && !firstAttempt) {
|
||||
// Send a message announcing that we are retrying the call
|
||||
SendMessage(
|
||||
runContext,
|
||||
string.Format(
|
||||
Resources.RetryRemoteCall,
|
||||
currentTest != null ? currentTest.HumanReadableId : caller
|
||||
),
|
||||
currentTest
|
||||
);
|
||||
}
|
||||
firstAttempt = false;
|
||||
|
||||
if (!IsClientAlive()) {
|
||||
Close();
|
||||
|
||||
if (restartVS && currentTest != null) {
|
||||
SendMessage(runContext, "Restarting VS", currentTest);
|
||||
InitializeForTest(currentTest, runContext);
|
||||
} else {
|
||||
SendMessage(runContext, Resources.NoClient, currentTest);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var remote = _remote;
|
||||
if (remote == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
action(remote);
|
||||
return true;
|
||||
} catch (RemotingException ex) {
|
||||
#if DEBUG
|
||||
var msg = string.Format(Resources.RemotingErrorDebug, caller, ex.Message, ex.ToString());
|
||||
#else
|
||||
var msg = string.Format(Resources.RemotingError, caller, ex.Message);
|
||||
#endif
|
||||
SendMessage(runContext, msg, currentTest);
|
||||
|
||||
// Close _remote and let EnsureClient bring it back if
|
||||
// requested by the caller
|
||||
var disposableRemote = Interlocked.Exchange(ref _remote, null) as IDisposable;
|
||||
if (disposableRemote != null) {
|
||||
try {
|
||||
disposableRemote.Dispose();
|
||||
} catch (RemotingException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(Resources.NoClient);
|
||||
}
|
||||
|
||||
|
||||
#region ITestAdapter members
|
||||
|
||||
public void Initialize(IRunContext runContext) {
|
||||
_runContext = runContext;
|
||||
_runId = runContext.RunConfig.TestRun.Id;
|
||||
}
|
||||
|
||||
public void Cleanup() {
|
||||
RemoteCall(r => r.Cleanup());
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
public void PreTestRunFinished(IRunContext runContext) {
|
||||
RemoteCall(r => r.PreTestRunFinished(runContext));
|
||||
}
|
||||
|
||||
public void ReceiveMessage(object message) {
|
||||
RemoteCall(r => r.ReceiveMessage(message));
|
||||
}
|
||||
|
||||
|
||||
public void AbortTestRun() {
|
||||
RemoteCall(r => r.AbortTestRun(), restartVS: false);
|
||||
}
|
||||
|
||||
public void PauseTestRun() {
|
||||
RemoteCall(r => r.PauseTestRun(), restartVS: false);
|
||||
}
|
||||
|
||||
public void ResumeTestRun() {
|
||||
if (!RemoteCall(r => r.ResumeTestRun())) {
|
||||
throw new InvalidOperationException(string.Format(Resources.FailedToResume));
|
||||
}
|
||||
}
|
||||
|
||||
public void Run(ITestElement testElement, ITestContext testContext) {
|
||||
if (!RemoteCall(r => r.Run(testElement, testContext), currentTest: testElement)) {
|
||||
testContext.ResultSink.AddResult(new TestResult(".", _runId, testElement) {
|
||||
Outcome = TestOutcome.NotRunnable
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void StopTestRun() {
|
||||
RemoteCall(r => r.StopTestRun(), restartVS: false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost {
|
||||
public static class VSTestProperties {
|
||||
public static class ScreenCapture {
|
||||
public const string Key = "ScreenCapture";
|
||||
public const string IntervalKey = "ScreenCaptureInterval";
|
||||
}
|
||||
|
||||
public static class VSApplication {
|
||||
public const string Key = "VSApplication";
|
||||
public const string VisualStudio = "VisualStudio";
|
||||
public const string WDExpress = "WDExpress";
|
||||
public const string VWDExpress = "VWDExpress";
|
||||
public const string Mock = "Mock";
|
||||
}
|
||||
|
||||
public static class VSExecutable {
|
||||
public const string Key = "VSExecutable";
|
||||
public const string DevEnv = "devenv";
|
||||
public const string WDExpress = "wdexpress";
|
||||
public const string VWDExpress = "vwdexpress";
|
||||
}
|
||||
|
||||
public static class VSVersion {
|
||||
public const string Key = "VSVersion";
|
||||
}
|
||||
|
||||
public static class VSHive {
|
||||
public const string Key = "VSHive";
|
||||
public const string Exp = "Exp";
|
||||
}
|
||||
|
||||
public static class VSLaunchTimeoutInSeconds {
|
||||
public const string Key = "VSLaunchTimeoutInSeconds";
|
||||
}
|
||||
|
||||
public static class VSDebugMixedMode {
|
||||
public const string Key = "VSDebugMixedMode";
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost {
|
||||
public static class VSTestProperties {
|
||||
public static class ScreenCapture {
|
||||
public const string Key = "ScreenCapture";
|
||||
public const string IntervalKey = "ScreenCaptureInterval";
|
||||
}
|
||||
|
||||
public static class VSApplication {
|
||||
public const string Key = "VSApplication";
|
||||
public const string VisualStudio = "VisualStudio";
|
||||
public const string WDExpress = "WDExpress";
|
||||
public const string VWDExpress = "VWDExpress";
|
||||
public const string Mock = "Mock";
|
||||
}
|
||||
|
||||
public static class VSExecutable {
|
||||
public const string Key = "VSExecutable";
|
||||
public const string DevEnv = "devenv";
|
||||
public const string WDExpress = "wdexpress";
|
||||
public const string VWDExpress = "vwdexpress";
|
||||
}
|
||||
|
||||
public static class VSVersion {
|
||||
public const string Key = "VSVersion";
|
||||
}
|
||||
|
||||
public static class VSHive {
|
||||
public const string Key = "VSHive";
|
||||
public const string Exp = "Exp";
|
||||
}
|
||||
|
||||
public static class VSLaunchTimeoutInSeconds {
|
||||
public const string Key = "VSLaunchTimeoutInSeconds";
|
||||
}
|
||||
|
||||
public static class VSDebugMixedMode {
|
||||
public const string Key = "VSDebugMixedMode";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,273 +1,273 @@
|
|||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EnvDTE;
|
||||
using Microsoft.VisualStudio.OLE.Interop;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.Win32;
|
||||
using Process = System.Diagnostics.Process;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
class VisualStudio : IDisposable {
|
||||
private readonly Version _version;
|
||||
private readonly int _processId;
|
||||
private readonly bool _killOnDispose;
|
||||
private DTE _dte;
|
||||
|
||||
public VisualStudio(int processId, Version version, bool killOnDispose = false) {
|
||||
_processId = processId;
|
||||
_version = version;
|
||||
_killOnDispose = killOnDispose;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
MessageFilter.Revoke();
|
||||
if (_killOnDispose) {
|
||||
try {
|
||||
Process.GetProcessById(_processId).Kill();
|
||||
} catch (ArgumentException) {
|
||||
} catch (InvalidOperationException) {
|
||||
} catch (Win32Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~VisualStudio() {
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public int ProcessId {
|
||||
get { return _processId; }
|
||||
}
|
||||
|
||||
public static async Task<VisualStudio> LaunchAsync(
|
||||
string application,
|
||||
string executable,
|
||||
Version version,
|
||||
string hive,
|
||||
CancellationToken cancel
|
||||
) {
|
||||
var installDir = GetInstallDir(application, version, hive);
|
||||
if (!Directory.Exists(installDir)) {
|
||||
throw new DirectoryNotFoundException("Cannot find Visual Studio install path");
|
||||
}
|
||||
|
||||
var devenv = Path.Combine(installDir, executable);
|
||||
if (!File.Exists(devenv)) {
|
||||
throw new FileNotFoundException("Cannot find Visual Studio executable");
|
||||
}
|
||||
|
||||
var psi = new ProcessStartInfo(devenv);
|
||||
if (!string.IsNullOrEmpty(hive)) {
|
||||
psi.Arguments = "/rootSuffix " + hive;
|
||||
}
|
||||
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
var process = Process.Start(psi);
|
||||
|
||||
await Task.Run(() => process.WaitForInputIdle(), cancel);
|
||||
|
||||
var vs = new VisualStudio(process.Id, version, true);
|
||||
try {
|
||||
while (true) {
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
if (vs.DTE != null) {
|
||||
return Interlocked.Exchange(ref vs, null);
|
||||
}
|
||||
await Task.Delay(1000, cancel);
|
||||
}
|
||||
} finally {
|
||||
if (vs != null) {
|
||||
vs.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetInstallDir(string application, Version version, string hive) {
|
||||
using (var root = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
|
||||
using (var key = root.OpenSubKey("Software\\Microsoft\\" + application)) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string installDir;
|
||||
|
||||
using (var subkey = key.OpenSubKey(version.ToString())) {
|
||||
if (subkey == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
installDir = subkey.GetValue("InstallDir") as string;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(installDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return installDir;
|
||||
}
|
||||
}
|
||||
|
||||
public DTE DTE {
|
||||
get {
|
||||
if (_dte == null) {
|
||||
_dte = GetDTE(_processId, _version);
|
||||
}
|
||||
return _dte;
|
||||
}
|
||||
}
|
||||
|
||||
public T GetService<T>(Type type = null) where T : class {
|
||||
var sp = ((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)DTE);
|
||||
return new ServiceProvider(sp).GetService(type ?? typeof(T)) as T;
|
||||
}
|
||||
|
||||
// Source from
|
||||
// http://blogs.msdn.com/b/kirillosenkov/archive/2011/08/10/how-to-get-dte-from-visual-studio-process-id.aspx
|
||||
[DllImport("ole32.dll")]
|
||||
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
|
||||
|
||||
private static DTE GetDTE(int processId, Version version) {
|
||||
MessageFilter.Register();
|
||||
|
||||
var process = Process.GetProcessById(processId);
|
||||
string progIdName = "VisualStudio";
|
||||
|
||||
switch (process.MainModule.ModuleName.ToLowerInvariant()) {
|
||||
case "wdexpress.exe":
|
||||
progIdName = "WDExpress";
|
||||
break;
|
||||
case "vwdexpress.exe":
|
||||
progIdName = "VWDExpress";
|
||||
break;
|
||||
}
|
||||
|
||||
string progId = string.Format("!{0}.DTE.{1}:{2}", progIdName, version, processId);
|
||||
object runningObject = null;
|
||||
|
||||
IBindCtx bindCtx = null;
|
||||
IRunningObjectTable rot = null;
|
||||
IEnumMoniker enumMonikers = null;
|
||||
|
||||
Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
|
||||
bindCtx.GetRunningObjectTable(out rot);
|
||||
rot.EnumRunning(out enumMonikers);
|
||||
|
||||
IMoniker[] moniker = new IMoniker[1];
|
||||
uint numberFetched = 0;
|
||||
while (enumMonikers.Next(1, moniker, out numberFetched) == 0) {
|
||||
IMoniker runningObjectMoniker = moniker[0];
|
||||
|
||||
string name = null;
|
||||
|
||||
try {
|
||||
if (runningObjectMoniker != null) {
|
||||
runningObjectMoniker.GetDisplayName(bindCtx, null, out name);
|
||||
}
|
||||
} catch (UnauthorizedAccessException) {
|
||||
// Do nothing, there is something in the ROT that we do not have access to.
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name) && string.Equals(name, progId, StringComparison.Ordinal)) {
|
||||
rot.GetObject(runningObjectMoniker, out runningObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (DTE)runningObject;
|
||||
}
|
||||
|
||||
public class MessageFilter : IOleMessageFilter {
|
||||
// Start the filter.
|
||||
public static void Register() {
|
||||
IOleMessageFilter newFilter = new MessageFilter();
|
||||
IOleMessageFilter oldFilter = null;
|
||||
CoRegisterMessageFilter(newFilter, out oldFilter);
|
||||
}
|
||||
|
||||
// Done with the filter, close it.
|
||||
public static void Revoke() {
|
||||
IOleMessageFilter oldFilter = null;
|
||||
CoRegisterMessageFilter(null, out oldFilter);
|
||||
}
|
||||
|
||||
const int SERVERCALL_ISHANDLED = 0;
|
||||
const int SERVERCALL_RETRYLATER = 2;
|
||||
const int PENDINGMSG_WAITDEFPROCESS = 2;
|
||||
|
||||
private MessageFilter() { }
|
||||
|
||||
// IOleMessageFilter functions.
|
||||
// Handle incoming thread requests.
|
||||
int IOleMessageFilter.HandleInComingCall(int dwCallType,
|
||||
IntPtr hTaskCaller,
|
||||
int dwTickCount,
|
||||
IntPtr lpInterfaceInfo) {
|
||||
return SERVERCALL_ISHANDLED;
|
||||
}
|
||||
|
||||
// Thread call was rejected, so try again.
|
||||
int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) {
|
||||
if (dwRejectType == SERVERCALL_RETRYLATER && dwTickCount < 10000) {
|
||||
// Retry the thread call after 250ms
|
||||
return 250;
|
||||
}
|
||||
// Too busy; cancel call.
|
||||
return -1;
|
||||
}
|
||||
|
||||
int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType) {
|
||||
return PENDINGMSG_WAITDEFPROCESS;
|
||||
}
|
||||
|
||||
// Implement the IOleMessageFilter interface.
|
||||
[DllImport("Ole32.dll")]
|
||||
private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("00000016-0000-0000-C000-000000000046")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IOleMessageFilter {
|
||||
[PreserveSig]
|
||||
int HandleInComingCall(int dwCallType,
|
||||
IntPtr hTaskCaller,
|
||||
int dwTickCount,
|
||||
IntPtr lpInterfaceInfo);
|
||||
|
||||
[PreserveSig]
|
||||
int RetryRejectedCall(IntPtr hTaskCallee,
|
||||
int dwTickCount,
|
||||
int dwRejectType);
|
||||
|
||||
[PreserveSig]
|
||||
int MessagePending(IntPtr hTaskCallee,
|
||||
int dwTickCount,
|
||||
int dwPendingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ****************************************************************************
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
|
||||
* copy of the license can be found in the License.html file at the root of this distribution. If
|
||||
* you cannot locate the Apache License, Version 2.0, please send an email to
|
||||
* vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
|
||||
* by the terms of the Apache License, Version 2.0.
|
||||
*
|
||||
* You must not remove this notice, or any other, from this software.
|
||||
*
|
||||
* ***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using EnvDTE;
|
||||
using Microsoft.VisualStudio.OLE.Interop;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.Win32;
|
||||
using Process = System.Diagnostics.Process;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Microsoft.VisualStudioTools.VSTestHost.Internal {
|
||||
class VisualStudio : IDisposable {
|
||||
private readonly Version _version;
|
||||
private readonly int _processId;
|
||||
private readonly bool _killOnDispose;
|
||||
private DTE _dte;
|
||||
|
||||
public VisualStudio(int processId, Version version, bool killOnDispose = false) {
|
||||
_processId = processId;
|
||||
_version = version;
|
||||
_killOnDispose = killOnDispose;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
MessageFilter.Revoke();
|
||||
if (_killOnDispose) {
|
||||
try {
|
||||
Process.GetProcessById(_processId).Kill();
|
||||
} catch (ArgumentException) {
|
||||
} catch (InvalidOperationException) {
|
||||
} catch (Win32Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~VisualStudio() {
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public int ProcessId {
|
||||
get { return _processId; }
|
||||
}
|
||||
|
||||
public static async Task<VisualStudio> LaunchAsync(
|
||||
string application,
|
||||
string executable,
|
||||
Version version,
|
||||
string hive,
|
||||
CancellationToken cancel
|
||||
) {
|
||||
var installDir = GetInstallDir(application, version, hive);
|
||||
if (!Directory.Exists(installDir)) {
|
||||
throw new DirectoryNotFoundException("Cannot find Visual Studio install path");
|
||||
}
|
||||
|
||||
var devenv = Path.Combine(installDir, executable);
|
||||
if (!File.Exists(devenv)) {
|
||||
throw new FileNotFoundException("Cannot find Visual Studio executable");
|
||||
}
|
||||
|
||||
var psi = new ProcessStartInfo(devenv);
|
||||
if (!string.IsNullOrEmpty(hive)) {
|
||||
psi.Arguments = "/rootSuffix " + hive;
|
||||
}
|
||||
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
var process = Process.Start(psi);
|
||||
|
||||
await Task.Run(() => process.WaitForInputIdle(), cancel);
|
||||
|
||||
var vs = new VisualStudio(process.Id, version, true);
|
||||
try {
|
||||
while (true) {
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
if (vs.DTE != null) {
|
||||
return Interlocked.Exchange(ref vs, null);
|
||||
}
|
||||
await Task.Delay(1000, cancel);
|
||||
}
|
||||
} finally {
|
||||
if (vs != null) {
|
||||
vs.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetInstallDir(string application, Version version, string hive) {
|
||||
using (var root = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
|
||||
using (var key = root.OpenSubKey("Software\\Microsoft\\" + application)) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string installDir;
|
||||
|
||||
using (var subkey = key.OpenSubKey(version.ToString())) {
|
||||
if (subkey == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
installDir = subkey.GetValue("InstallDir") as string;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(installDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return installDir;
|
||||
}
|
||||
}
|
||||
|
||||
public DTE DTE {
|
||||
get {
|
||||
if (_dte == null) {
|
||||
_dte = GetDTE(_processId, _version);
|
||||
}
|
||||
return _dte;
|
||||
}
|
||||
}
|
||||
|
||||
public T GetService<T>(Type type = null) where T : class {
|
||||
var sp = ((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)DTE);
|
||||
return new ServiceProvider(sp).GetService(type ?? typeof(T)) as T;
|
||||
}
|
||||
|
||||
// Source from
|
||||
// http://blogs.msdn.com/b/kirillosenkov/archive/2011/08/10/how-to-get-dte-from-visual-studio-process-id.aspx
|
||||
[DllImport("ole32.dll")]
|
||||
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
|
||||
|
||||
private static DTE GetDTE(int processId, Version version) {
|
||||
MessageFilter.Register();
|
||||
|
||||
var process = Process.GetProcessById(processId);
|
||||
string progIdName = "VisualStudio";
|
||||
|
||||
switch (process.MainModule.ModuleName.ToLowerInvariant()) {
|
||||
case "wdexpress.exe":
|
||||
progIdName = "WDExpress";
|
||||
break;
|
||||
case "vwdexpress.exe":
|
||||
progIdName = "VWDExpress";
|
||||
break;
|
||||
}
|
||||
|
||||
string progId = string.Format("!{0}.DTE.{1}:{2}", progIdName, version, processId);
|
||||
object runningObject = null;
|
||||
|
||||
IBindCtx bindCtx = null;
|
||||
IRunningObjectTable rot = null;
|
||||
IEnumMoniker enumMonikers = null;
|
||||
|
||||
Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
|
||||
bindCtx.GetRunningObjectTable(out rot);
|
||||
rot.EnumRunning(out enumMonikers);
|
||||
|
||||
IMoniker[] moniker = new IMoniker[1];
|
||||
uint numberFetched = 0;
|
||||
while (enumMonikers.Next(1, moniker, out numberFetched) == 0) {
|
||||
IMoniker runningObjectMoniker = moniker[0];
|
||||
|
||||
string name = null;
|
||||
|
||||
try {
|
||||
if (runningObjectMoniker != null) {
|
||||
runningObjectMoniker.GetDisplayName(bindCtx, null, out name);
|
||||
}
|
||||
} catch (UnauthorizedAccessException) {
|
||||
// Do nothing, there is something in the ROT that we do not have access to.
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name) && string.Equals(name, progId, StringComparison.Ordinal)) {
|
||||
rot.GetObject(runningObjectMoniker, out runningObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (DTE)runningObject;
|
||||
}
|
||||
|
||||
public class MessageFilter : IOleMessageFilter {
|
||||
// Start the filter.
|
||||
public static void Register() {
|
||||
IOleMessageFilter newFilter = new MessageFilter();
|
||||
IOleMessageFilter oldFilter = null;
|
||||
CoRegisterMessageFilter(newFilter, out oldFilter);
|
||||
}
|
||||
|
||||
// Done with the filter, close it.
|
||||
public static void Revoke() {
|
||||
IOleMessageFilter oldFilter = null;
|
||||
CoRegisterMessageFilter(null, out oldFilter);
|
||||
}
|
||||
|
||||
const int SERVERCALL_ISHANDLED = 0;
|
||||
const int SERVERCALL_RETRYLATER = 2;
|
||||
const int PENDINGMSG_WAITDEFPROCESS = 2;
|
||||
|
||||
private MessageFilter() { }
|
||||
|
||||
// IOleMessageFilter functions.
|
||||
// Handle incoming thread requests.
|
||||
int IOleMessageFilter.HandleInComingCall(int dwCallType,
|
||||
IntPtr hTaskCaller,
|
||||
int dwTickCount,
|
||||
IntPtr lpInterfaceInfo) {
|
||||
return SERVERCALL_ISHANDLED;
|
||||
}
|
||||
|
||||
// Thread call was rejected, so try again.
|
||||
int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) {
|
||||
if (dwRejectType == SERVERCALL_RETRYLATER && dwTickCount < 10000) {
|
||||
// Retry the thread call after 250ms
|
||||
return 250;
|
||||
}
|
||||
// Too busy; cancel call.
|
||||
return -1;
|
||||
}
|
||||
|
||||
int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType) {
|
||||
return PENDINGMSG_WAITDEFPROCESS;
|
||||
}
|
||||
|
||||
// Implement the IOleMessageFilter interface.
|
||||
[DllImport("Ole32.dll")]
|
||||
private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("00000016-0000-0000-C000-000000000046")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IOleMessageFilter {
|
||||
[PreserveSig]
|
||||
int HandleInComingCall(int dwCallType,
|
||||
IntPtr hTaskCaller,
|
||||
int dwTickCount,
|
||||
IntPtr lpInterfaceInfo);
|
||||
|
||||
[PreserveSig]
|
||||
int RetryRejectedCall(IntPtr hTaskCallee,
|
||||
int dwTickCount,
|
||||
int dwRejectType);
|
||||
|
||||
[PreserveSig]
|
||||
int MessagePending(IntPtr hTaskCallee,
|
||||
int dwTickCount,
|
||||
int dwPendingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче