Merge pull request #7321 from dotnet/merges/release/2.0.0-to-master-20170731-070024

Merge release/2.0.0 to master
This commit is contained in:
Livar 2017-07-31 12:00:50 -07:00 committed by GitHub
commit 51567d7641
73 changed files with 1717 additions and 157 deletions

View file

@ -4,15 +4,15 @@
<CLI_SharedFrameworkVersion>2.0.0</CLI_SharedFrameworkVersion>
<CLI_MSBuild_Version>15.3.409</CLI_MSBuild_Version>
<CLI_Roslyn_Version>2.3.2-beta1-61921-05</CLI_Roslyn_Version>
<CLI_Roslyn_Satellites_Version>2.3.0-pre-20170624-6</CLI_Roslyn_Satellites_Version>
<CLI_Roslyn_Satellites_Version>2.3.0-pre-20170727-1</CLI_Roslyn_Satellites_Version>
<CLI_DiaSymNative_Version>1.6.0-beta2-25304</CLI_DiaSymNative_Version>
<CLI_FSharp_Version>4.2.0-rc-170630-0</CLI_FSharp_Version>
<CLI_FSharp_Satellites_Version>4.4.1-pre-20170624-6</CLI_FSharp_Satellites_Version>
<CLI_FSharp_Satellites_Version>4.4.1-pre-20170727-1</CLI_FSharp_Satellites_Version>
<!-- We'll usually want to keep these versions in sync, but we may want to diverge in some
cases, so use separate properties but derive one from the other unless we want to
explicitly use different versions. -->
<CLI_NETSDK_Version>2.0.0-preview3-20170721-7</CLI_NETSDK_Version>
<CLI_NETSDK_Version>2.0.0-preview3-20170728-1</CLI_NETSDK_Version>
<CLI_MSBuildExtensions_Version>$(CLI_NETSDK_Version)</CLI_MSBuildExtensions_Version>
<CLI_NuGet_Version>4.3.0-rtm-4324</CLI_NuGet_Version>
@ -23,14 +23,14 @@
<SharedHostVersion>$(CLI_SharedFrameworkVersion)</SharedHostVersion>
<HostFxrVersion>$(CLI_SharedFrameworkVersion)</HostFxrVersion>
<TemplateEngineVersion>1.0.0-beta2-20170719-291</TemplateEngineVersion>
<TemplateEngineTemplateVersion>1.0.0-beta2-20170725-300</TemplateEngineTemplateVersion>
<TemplateEngineTemplate2_0Version>1.0.0-beta2-20170725-300</TemplateEngineTemplate2_0Version>
<TemplateEngineTemplateVersion>1.0.0-beta2-20170727-301</TemplateEngineTemplateVersion>
<TemplateEngineTemplate2_0Version>1.0.0-beta2-20170727-301</TemplateEngineTemplate2_0Version>
<PlatformAbstractionsVersion>2.0.0</PlatformAbstractionsVersion>
<DependencyModelVersion>2.0.0</DependencyModelVersion>
<CliCommandLineParserVersion>0.1.1-alpha-167</CliCommandLineParserVersion>
<CliMigrateVersion>1.2.1-alpha-002133</CliMigrateVersion>
<MicroBuildVersion>0.2.0</MicroBuildVersion>
<SpaTemplateVersion>1.0.0-preview-000409</SpaTemplateVersion>
<SpaTemplateVersion>1.0.417</SpaTemplateVersion>
<XliffTasksVersion>0.2.0-beta-000042</XliffTasksVersion>
<!-- This should either be timestamped or notimestamp as appropriate -->

View file

@ -43,13 +43,15 @@
SkipUnchangedFiles="False"
UseHardlinksIfPossible="False" />
<!-- Create layout: postinst -->
<Copy
DestinationFiles= "$(DebianPostinstFile)"
SourceFiles="$(DebianPostinstTemplateFile)"
OverwriteReadOnlyFiles="True"
SkipUnchangedFiles="False"
UseHardlinksIfPossible="False" />
<!-- Create layout: Generate and Place postinst -->
<ReplaceFileContents
InputFile="$(DebianPostinstTemplateFile)"
DestinationFile="$(DebianPostinstFile)"
ReplacementItems="@(DebianPostInstTokenValues)" />
<Chmod
Glob="$(DebianPostinstFile)"
Mode="ugo+x" />
<!-- Create layout: Generate and Place debian_config.json -->
<ReplaceFileContents

View file

@ -36,9 +36,10 @@
<HostFxrDebianPackageName>$(HostFxrDebianPackageName.ToLower())</HostFxrDebianPackageName>
<HostDebianPackageName>dotnet-host</HostDebianPackageName>
<AspNetCoreRuntimePackageName>$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersionAndRelease)-$(AspNetCoreRuntimePackageTimestamp)</AspNetCoreRuntimePackageName>
<AspNetCoreRuntimePackageName Condition=" '$(AspNetCoreRuntimePackageFlavor)' == 'notimestamp' ">$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersion)</AspNetCoreRuntimePackageName>
<HostRidInAspNetCoreRuntimeDebInstallerFileName>$(HostRid)</HostRidInAspNetCoreRuntimeDebInstallerFileName>
<HostRidInAspNetCoreRuntimeDebInstallerFileName Condition=" '$(HostRid)' == 'debian.8-x64' ">debian-x64</HostRidInAspNetCoreRuntimeDebInstallerFileName>
<AspNetCoreRuntimeDebInstallerFileName>$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersionAndRelease)-$(AspNetCoreRuntimePackageTimestamp)-$(HostRidInAspNetCoreRuntimeDebInstallerFileName).deb</AspNetCoreRuntimeDebInstallerFileName>
<AspNetCoreRuntimeDebInstallerFileName Condition=" '$(AspNetCoreRuntimePackageFlavor)' == 'notimestamp' ">$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersion)-$(HostRidInAspNetCoreRuntimeDebInstallerFileName).deb</AspNetCoreRuntimeDebInstallerFileName>
</PropertyGroup>
<!-- Inputs -->
@ -105,6 +106,10 @@
<DebianConfigTokenValues Include="%CLI_SDK_BRAND_NAME%">
<ReplacementString>$(SdkBrandName)</ReplacementString>
</DebianConfigTokenValues>
<DebianPostInstTokenValues Include="%SDK_VERSION%">
<ReplacementString>$(SdkVersion)</ReplacementString>
</DebianPostInstTokenValues>
</ItemGroup>
<ItemGroup>

View file

@ -14,6 +14,9 @@
<SdkPkgSourcesRootDirectory>$(RepoRoot)/packaging/osx/clisdk</SdkPkgSourcesRootDirectory>
<SdkPkgScriptsDirectory>$(SdkPkgSourcesRootDirectory)/scripts</SdkPkgScriptsDirectory>
<SdkPkgScriptTemplateFile>$(SdkPkgScriptsDirectory)/postinstall</SdkPkgScriptTemplateFile>
<SdkPkgDestinationScriptsDirectory>$(PkgIntermediateDirectory)/scripts</SdkPkgDestinationScriptsDirectory>
<SdkPkgScriptFile>$(SdkPkgDestinationScriptsDirectory)/postinstall</SdkPkgScriptFile>
<SdkProductArchiveResourcesDirectory>$(SdkPkgSourcesRootDirectory)/resources</SdkProductArchiveResourcesDirectory>
<SdkProductArchiveDistributionTemplateFile>$(SdkPkgSourcesRootDirectory)/Distribution-Template</SdkProductArchiveDistributionTemplateFile>
@ -54,6 +57,10 @@
<DistributionTemplateReplacement Include="{HostFxrBrandName}">
<ReplacementString>$(HostFxrBrandName)</ReplacementString>
</DistributionTemplateReplacement>
<PostInstallScriptReplacement Include="%SDK_VERSION%">
<ReplacementString>$(SdkVersion)</ReplacementString>
</PostInstallScriptReplacement>
</ItemGroup>
<!-- Consumed By Publish -->
@ -83,12 +90,22 @@
<Copy SourceFiles="@(AspNetRuntimeFilesInput)"
DestinationFiles="@(AspNetRuntimeFilesInput->'$(SdkLayoutOutputDirectory)/%(RecursiveDir)%(FileName)%(Extension)')" />
<ReplaceFileContents
InputFile="$(SdkPkgScriptTemplateFile)"
DestinationFile="$(SdkPkgScriptFile)"
ReplacementPatterns="@(PostInstallScriptReplacement -> '%(Identity)')"
ReplacementStrings="@(PostInstallScriptReplacement -> '%(ReplacementString)')" />
<Chmod
Glob="$(SdkPkgScriptFile)"
Mode="ugo+x" />
<Exec Command="pkgbuild
--root '$(SdkLayoutOutputDirectory)'
--identifier '$(SdkComponentId)'
--version '$(SdkVersion)'
--install-location '$(PkgInstallDirectory)'
--scripts '$(SdkPkgScriptsDirectory)'
--scripts '$(SdkPkgDestinationScriptsDirectory)'
'$(SdkInstallerFile)'" />
</Target>

View file

@ -4,5 +4,6 @@
<RpmConfigJsonName>rpm_config.json</RpmConfigJsonName>
<TemplatesDir>$(RepoRoot)/packaging/rpm/templates</TemplatesDir>
<ScriptsDir>$(RepoRoot)/packaging/rpm/scripts</ScriptsDir>
<AfterInstallHostScriptName>after_install_host.sh</AfterInstallHostScriptName>
</PropertyGroup>
</Project>

View file

@ -51,10 +51,14 @@
<HostFxrRpmPackageName>$(HostFxrRpmPackageName.ToLower())</HostFxrRpmPackageName>
<HostRpmPackageName>dotnet-host</HostRpmPackageName>
<HostRidInAspNetCoreRuntimeRpmInstallerFileName>$(HostRid)</HostRidInAspNetCoreRuntimeRpmInstallerFileName>
<HostRidInAspNetCoreRuntimeRpmInstallerFileName Condition=" '$(HostRid)' == 'rhel.7-x64' ">rhel-x64</HostRidInAspNetCoreRuntimeRpmInstallerFileName>
<AspNetCoreRuntimePackageName>$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersionAndRelease)-$(AspNetCoreRuntimePackageTimestamp)</AspNetCoreRuntimePackageName>
<AspNetCoreRuntimePackageName Condition="'$(AspNetCoreRuntimePackageFlavor)' == 'notimestamp'">$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersion)</AspNetCoreRuntimePackageName>
<AspNetCoreRuntimePackageVersion>$(AspNetCoreVersion)-$(AspNetCoreRelease)-$(AspNetCoreRuntimePackageTimestamp)</AspNetCoreRuntimePackageVersion>
<AspNetCoreRuntimePackageVersion Condition="'$(AspNetCoreRuntimePackageFlavor)' == 'notimestamp'">$(AspNetCoreVersion)</AspNetCoreRuntimePackageVersion>
<AspNetCoreRuntimeRpmInstallerFileName>$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersionAndRelease)-$(AspNetCoreRuntimePackageTimestamp)-$(HostRidInAspNetCoreRuntimeRpmInstallerFileName).rpm</AspNetCoreRuntimeRpmInstallerFileName>
<AspNetCoreRuntimeRpmInstallerFileName Condition="'$(AspNetCoreRuntimePackageFlavor)' == 'notimestamp'">$(AspNetCoreRuntimePackageBrandName)-$(AspNetCoreVersion)-$(HostRidInAspNetCoreRuntimeRpmInstallerFileName).rpm</AspNetCoreRuntimeRpmInstallerFileName>
<AfterInstallHostScriptTemplateFile>$(ScriptsDir)/$(AfterInstallHostScriptName)</AfterInstallHostScriptTemplateFile>
<AfterInstallHostScriptDestinationFile>$(RpmLayoutScripts)/$(AfterInstallHostScriptName)</AfterInstallHostScriptDestinationFile>
</PropertyGroup>
<PropertyGroup>
@ -78,7 +82,7 @@
<SDKFiles Include="$(InputRoot)/**/*"/>
<SDKManpages Include="$(ManPagesDir)/**/*"/>
<SDKTemplatesFiles Include="$(TemplatesDir)/**/*"/>
<SDKScriptsFiles Include="$(ScriptsDir)/**/*"/>
<SDKScriptsFiles Include="$(ScriptsDir)/after_remove_host.sh"/>
</ItemGroup>
<Copy SourceFiles="@(SDKFiles)"
@ -128,6 +132,10 @@
<SDKTokenValue Include="%SDK_RPM_PACKAGE_NAME%">
<ReplacementString>$(SdkRpmPackageName)</ReplacementString>
</SDKTokenValue>
<AfterInstallHostTokenValue Include="%SDK_VERSION%">
<ReplacementString>$(SdkVersion)</ReplacementString>
</AfterInstallHostTokenValue>
</ItemGroup>
<ItemGroup>
@ -135,6 +143,14 @@
<GeneratedInstallers Include="$(SdkInstallerFile)" />
</ItemGroup>
<ReplaceFileContents InputFile="$(AfterInstallHostScriptTemplateFile)"
DestinationFile="$(AfterInstallHostScriptDestinationFile)"
ReplacementItems="@(AfterInstallHostTokenValue)"/>
<Chmod
Glob="$(AfterInstallHostScriptDestinationFile)"
Mode="ugo+x" />
<ReplaceFileContents InputFile="$(ConfigJsonFile)"
DestinationFile="$(RpmLayoutDirectory)$(RpmConfigJsonName)"
ReplacementItems="@(SDKTokenValue)"/>
@ -229,4 +245,4 @@
<Exec Command="sudo yum remove -y $(HostFxrRpmPackageName)" />
<Exec Command="sudo yum remove -y $(HostRpmPackageName)" />
</Target>
</Project>
</Project>

View file

@ -19,4 +19,4 @@ Installation Note
--------------
A command will be run during the install process that will improve project restore speed and enable offline access. It will take up to a minute to complete."
dotnet internal-reportinstallsuccess "debianpackage" > /dev/null 2>&1 || true
/usr/share/dotnet/dotnet exec /usr/share/dotnet/sdk/%SDK_VERSION%/dotnet.dll internal-reportinstallsuccess "debianpackage" > /dev/null 2>&1 || true

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -13,6 +13,13 @@
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica;padding-left:10px">
<p>This product collects usage data</h2>
<ul>
<li><a href="https://aka.ms/dotnet-cli-telemetry">More information and opt-out</a></li>
</ul>
</div>
<br>
<div align="left" style="font-family: Helvetica">
<h2 style="padding-left:10px"> Resources </h2>
<ul>
@ -20,7 +27,6 @@
<li><a href="https://aka.ms/dotnet-cli-docs">SDK Documentation</a></li>
<li><a href="https://aka.ms/20-p2-rel-notes">Release Notes</a></li>
<li><a href="https://aka.ms/dotnet-tutorials">Tutorials</a></li>
<li><a href="https://aka.ms/dotnet-cli-telemetry">.NET Core Telemetry</a></li>
</ul>
</div>
</body>

View file

@ -11,6 +11,6 @@ INSTALL_DESTINATION=$2
# A temporary fix for the permissions issue(s)
chmod -R 755 $INSTALL_DESTINATION
$INSTALL_DESTINATION/dotnet internal-reportinstallsuccess "$1" > /dev/null 2>&1 || true
$INSTALL_DESTINATION/dotnet exec $INSTALL_DESTINATION/sdk/%SDK_VERSION%/dotnet.dll internal-reportinstallsuccess "$1" > /dev/null 2>&1 || true
exit 0

View file

@ -23,4 +23,4 @@ Installation Note
--------------
A command will be run during the install process that will improve project restore speed and enable offline access. It will take up to a minute to complete."
dotnet internal-reportinstallsuccess "rpmpackage" > /dev/null 2>&1 || true
/usr/share/dotnet/dotnet exec /usr/share/dotnet/sdk/%SDK_VERSION%/dotnet.dll internal-reportinstallsuccess "rpmpackage" > /dev/null 2>&1 || true

View file

@ -54,13 +54,14 @@ The following were installed at [DOTNETHOME]
• .NET Core Runtime 2.0.0
• Runtime Package Store
Resources
This product collects usage data
• More information and opt-out https://aka.ms/dotnet-cli-telemetry
Resources
• Core Documentation https://aka.ms/dotnet-docs
• SDK Documentation https://aka.ms/dotnet-cli-docs
• Release Notes https://aka.ms/20-p2-rel-notes
• Tutorials https://aka.ms/dotnet-tutorials
• .NET Core Telemetry https://aka.ms/dotnet-cli-telemetry</String>
• Tutorials https://aka.ms/dotnet-tutorials</String>
<String Id="WelcomeHeaderMessage">.NET Core SDK</String>
<String Id="WelcomeDescription">
.NET Core is a development platform that you can use to build command-line applications, microservices and modern websites. It is open source, cross-platform, and supported by Microsoft. We hope you enjoy it!</String>

View file

@ -20,6 +20,7 @@
<Property Id="ProductCPU" Value="$(var.Platform)" />
<Property Id="RTM_ProductVersion" Value="$(var.Dotnet_ProductVersion)" />
<Property Id="MSIFASTINSTALL" Value="7" />
<Property Id="NUGETVERSION" Value="$(var.NugetVersion)" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.MicrosoftEula)" />
<Property Id="DOTNETEXE">
@ -32,7 +33,7 @@
<CustomAction Id="PropertyAssignPrimeCacheAndTelemetry"
Property="QtExecPrimeCacheAndTelemetryTarget"
Value="&quot;[DOTNETHOME]\dotnet.exe&quot; internal-reportinstallsuccess &quot;[EXEFULLPATH]&quot;"
Value="&quot;[DOTNETHOME]\dotnet.exe&quot; exec &quot;[DOTNETHOME]\sdk\[NUGETVERSION]\dotnet.dll&quot; internal-reportinstallsuccess &quot;[EXEFULLPATH]&quot;"
Execute="immediate" />
<CustomAction Id="QtExecPrimeCacheAndTelemetryTarget"
BinaryKey="WixCA"

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Rozbalení</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Dekomprese</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Aufklappen</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Dekomprimieren</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Expandiendo</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Descomprimiendo</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Développement</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Décompression</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Espansione</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Decompressione</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">展開中</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">圧縮解除中</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">확장하는 중</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">압축을 푸는 중</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Rozwijanie</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Dekompresja</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Expandindo</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Descompactando</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Идет расширение</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Идет извлечение</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">Genişletme</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">Daraltma</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">正在扩展</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">正在解压缩</target>
<note />
</trans-unit>
</body>

View file

@ -4,12 +4,12 @@
<body>
<trans-unit id="Expanding">
<source>Expanding</source>
<target state="new">Expanding</target>
<target state="translated">正在展開</target>
<note />
</trans-unit>
<trans-unit id="Decompressing">
<source>Decompressing</source>
<target state="new">Decompressing</target>
<target state="translated">正在解壓縮</target>
<note />
</trans-unit>
</body>

View file

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
namespace Microsoft.DotNet.Cli.Utils
{
public interface ITelemetryFilter
{
IEnumerable<ApplicationInsightsEntryFormat> Filter(object o);
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
namespace Microsoft.DotNet.Cli.Utils
{
public static class TelemetryEventEntry
{
public static event EventHandler<InstrumentationEventArgs> EntryPosted;
public static ITelemetryFilter TelemetryFilter { get; set; } = new BlockFilter();
public static void TrackEvent(
string eventName = null,
IDictionary<string, string> properties = null,
IDictionary<string, double> measurements = null)
{
EntryPosted?.Invoke(typeof(TelemetryEventEntry),
new InstrumentationEventArgs(eventName, properties, measurements));
}
public static void SendFiltered(object o = null)
{
if (o == null)
{
return;
}
foreach (ApplicationInsightsEntryFormat entry in TelemetryFilter.Filter(o))
{
TrackEvent(entry.EventName, entry.Properties, entry.Measurements);
}
}
public static void Subscribe(Action<string,
IDictionary<string, string>,
IDictionary<string, double>> subscriber)
{
void Handler(object sender, InstrumentationEventArgs eventArgs)
{
subscriber(eventArgs.EventName, eventArgs.Properties, eventArgs.Measurements);
}
EntryPosted += Handler;
}
}
public class BlockFilter : ITelemetryFilter
{
public IEnumerable<ApplicationInsightsEntryFormat> Filter(object o)
{
return new List<ApplicationInsightsEntryFormat>();
}
}
public class InstrumentationEventArgs : EventArgs
{
internal InstrumentationEventArgs(
string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
EventName = eventName;
Properties = properties;
Measurements = measurements;
}
public string EventName { get; }
public IDictionary<string, string> Properties { get; }
public IDictionary<string, double> Measurements { get; }
}
public class ApplicationInsightsEntryFormat
{
public ApplicationInsightsEntryFormat(
string eventName = null,
IDictionary<string, string> properties = null,
IDictionary<string, double> measurements = null)
{
EventName = eventName;
Properties = properties;
Measurements = measurements;
}
public string EventName { get; }
public IDictionary<string, string> Properties { get; }
public IDictionary<string, double> Measurements { get; }
}
}

View file

@ -45,10 +45,12 @@ namespace Microsoft.DotNet.Configurer
{
PrintUnauthorizedAccessMessage();
}
else
{
PrintNugetCachePrimeMessage();
PrintNugetCachePrimeMessage();
_nugetCachePrimer.PrimeCache();
_nugetCachePrimer.PrimeCache();
}
}
}
@ -81,6 +83,8 @@ namespace Microsoft.DotNet.Configurer
private bool ShouldPrimeNugetCache()
{
return ShouldRunFirstRunExperience() &&
!_nugetCacheSentinel.Exists() &&
!_nugetCacheSentinel.InProgressSentinelAlreadyExists() &&
!_nugetCachePrimer.SkipPrimingTheCache();
}
@ -96,9 +100,7 @@ namespace Microsoft.DotNet.Configurer
var skipFirstTimeExperience =
_environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false);
return !skipFirstTimeExperience &&
!_nugetCacheSentinel.Exists() &&
!_nugetCacheSentinel.InProgressSentinelAlreadyExists();
return !skipFirstTimeExperience;
}
}
}

View file

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace Microsoft.DotNet.Configurer
{
public interface IUserLevelCacheWriter
{
string RunWithCache(string cacheKey, Func<string> getValueToCache);
}
}

View file

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.IO;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.Configuration;
namespace Microsoft.DotNet.Configurer
{
public class NoOpFirstTimeUseNoticeSentinel : IFirstTimeUseNoticeSentinel
{
public bool Exists()
{
return true;
}
public void CreateIfNotExists()
{
}
public void Dispose()
{
}
}
}

View file

@ -45,7 +45,7 @@ namespace Microsoft.DotNet.Configurer
public bool InProgressSentinelAlreadyExists()
{
return CouldNotGetAHandleToTheInProgressSentinel();
return CouldNotGetAHandleToTheInProgressSentinel() && !UnauthorizedAccess;
}
public bool Exists()

View file

@ -0,0 +1,73 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.IO;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.DotNet.Configurer
{
public class UserLevelCacheWriter : IUserLevelCacheWriter
{
private readonly IFile _file;
private readonly IDirectory _directory;
private string _dotnetUserProfileFolderPath;
public UserLevelCacheWriter(CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) :
this(
cliFallbackFolderPathCalculator.DotnetUserProfileFolderPath,
FileSystemWrapper.Default.File,
FileSystemWrapper.Default.Directory)
{
}
public string RunWithCache(string cacheKey, Func<string> getValueToCache)
{
var cacheFilepath = GetCacheFilePath(cacheKey);
try
{
if (!_file.Exists(cacheFilepath))
{
if (!_directory.Exists(_dotnetUserProfileFolderPath))
{
_directory.CreateDirectory(_dotnetUserProfileFolderPath);
}
var runResult = getValueToCache();
_file.WriteAllText(cacheFilepath, runResult);
return runResult;
}
else
{
return _file.ReadAllText(cacheFilepath);
}
}
catch (Exception ex)
{
if (ex is UnauthorizedAccessException
|| ex is PathTooLongException
|| ex is IOException)
{
return getValueToCache();
}
throw;
}
}
internal UserLevelCacheWriter(string dotnetUserProfileFolderPath, IFile file, IDirectory directory)
{
_file = file;
_directory = directory;
_dotnetUserProfileFolderPath = dotnetUserProfileFolderPath;
}
private string GetCacheFilePath(string cacheKey)
{
return Path.Combine(_dotnetUserProfileFolderPath, $"{Product.Version}_{cacheKey}.dotnetUserLevelCache");
}
}
}

View file

@ -2,9 +2,11 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.PlatformAbstractions;
@ -80,8 +82,10 @@ namespace Microsoft.DotNet.Cli
var lastArg = 0;
var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator();
using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator))
using (IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator))
using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel =
new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator))
{
IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel;
for (; lastArg < args.Length; lastArg++)
{
if (IsArg(args[lastArg], "d", "diagnostics"))
@ -100,8 +104,8 @@ namespace Microsoft.DotNet.Cli
return 0;
}
else if (IsArg(args[lastArg], "h", "help") ||
args[lastArg] == "-?" ||
args[lastArg] == "/?")
args[lastArg] == "-?" ||
args[lastArg] == "/?")
{
HelpCommand.PrintHelp();
return 0;
@ -113,10 +117,19 @@ namespace Microsoft.DotNet.Cli
}
else
{
ConfigureDotNetForFirstTimeUse(nugetCacheSentinel, firstTimeUseNoticeSentinel, cliFallbackFolderPathCalculator);
// It's the command, and we're done!
command = args[lastArg];
if (IsDotnetBeingInvokedFromNativeInstaller(command))
{
firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel();
}
ConfigureDotNetForFirstTimeUse(
nugetCacheSentinel,
firstTimeUseNoticeSentinel,
cliFallbackFolderPathCalculator);
break;
}
}
@ -128,11 +141,16 @@ namespace Microsoft.DotNet.Cli
if (telemetryClient == null)
{
telemetryClient = new Telemetry(firstTimeUseNoticeSentinel);
telemetryClient = new Telemetry.Telemetry(firstTimeUseNoticeSentinel);
}
TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent);
TelemetryEventEntry.TelemetryFilter = new TelemetryFilter();
}
var appArgs = (lastArg + 1) >= args.Length ? Enumerable.Empty<string>() : args.Skip(lastArg + 1).ToArray();
IEnumerable<string> appArgs =
(lastArg + 1) >= args.Length
? Enumerable.Empty<string>()
: args.Skip(lastArg + 1).ToArray();
if (CommandContext.IsVerbose())
{
@ -144,12 +162,12 @@ namespace Microsoft.DotNet.Cli
command = "help";
}
telemetryClient.TrackEvent(command, null, null);
TelemetryEventEntry.TrackEvent(command, null, null);
int exitCode;
BuiltInCommandMetadata builtIn;
if (BuiltInCommandsCatalog.Commands.TryGetValue(command, out builtIn))
if (BuiltInCommandsCatalog.Commands.TryGetValue(command, out var builtIn))
{
TelemetryEventEntry.SendFiltered(Parser.Instance.ParseFrom($"dotnet {command}", appArgs.ToArray()));
exitCode = builtIn.Command(appArgs.ToArray());
}
else
@ -161,9 +179,12 @@ namespace Microsoft.DotNet.Cli
.Execute();
exitCode = result.ExitCode;
}
return exitCode;
}
private static bool IsDotnetBeingInvokedFromNativeInstaller(string command)
{
return command == "internal-reportinstallsuccess";
}
private static void ConfigureDotNetForFirstTimeUse(

View file

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class AllowListToSendFirstAppliedOptions : IParseResultLogRule
{
public AllowListToSendFirstAppliedOptions(
HashSet<string> topLevelCommandNameAllowList)
{
_topLevelCommandNameAllowList = topLevelCommandNameAllowList;
}
private HashSet<string> _topLevelCommandNameAllowList { get; }
public List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult)
{
var topLevelCommandNameFromParse = parseResult["dotnet"]?.AppliedOptions?.FirstOrDefault()?.Name;
var result = new List<ApplicationInsightsEntryFormat>();
if (_topLevelCommandNameAllowList.Contains(topLevelCommandNameFromParse))
{
var firstOption = parseResult["dotnet"]?[topLevelCommandNameFromParse]
?.AppliedOptions?.FirstOrDefault()?.Name;
if (firstOption != null)
{
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandNameFromParse,
new Dictionary<string, string>
{
{"argument", firstOption}
}));
}
}
return result;
}
}
}

View file

@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class AllowListToSendFirstArgument : IParseResultLogRule
{
public AllowListToSendFirstArgument(
HashSet<string> topLevelCommandNameAllowList)
{
_topLevelCommandNameAllowList = topLevelCommandNameAllowList;
}
private HashSet<string> _topLevelCommandNameAllowList { get; }
public List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult)
{
var result = new List<ApplicationInsightsEntryFormat>();
var topLevelCommandNameFromParse = parseResult["dotnet"]?.AppliedOptions?.FirstOrDefault()?.Name;
if (topLevelCommandNameFromParse != null)
{
if (_topLevelCommandNameAllowList.Contains(topLevelCommandNameFromParse))
{
var firstArgument = parseResult["dotnet"][topLevelCommandNameFromParse].Arguments
?.FirstOrDefault();
if (firstArgument != null)
{
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandNameFromParse,
new Dictionary<string, string>
{
{"argument", firstArgument}
}));
}
}
}
return result;
}
}
}

View file

@ -0,0 +1,58 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.IO;
using System.Security;
using Microsoft.Win32;
using Microsoft.DotNet.PlatformAbstractions;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class DockerContainerDetectorForTelemetry : IDockerContainerDetector
{
public IsDockerContainer IsDockerContainer()
{
switch (RuntimeEnvironment.OperatingSystemPlatform)
{
case Platform.Windows:
try
{
using (RegistryKey subkey
= Registry.LocalMachine.OpenSubKey("System\\CurrentControlSet\\Control"))
{
return subkey?.GetValue("ContainerType") != null
? Cli.Telemetry.IsDockerContainer.True
: Cli.Telemetry.IsDockerContainer.False;
}
}
catch (SecurityException)
{
return Cli.Telemetry.IsDockerContainer.Unknown;
}
case Platform.Linux:
return ReadProcToDetectDockerInLinux()
? Cli.Telemetry.IsDockerContainer.True
: Cli.Telemetry.IsDockerContainer.False;
case Platform.Unknown:
return Cli.Telemetry.IsDockerContainer.Unknown;
case Platform.Darwin:
default:
return Cli.Telemetry.IsDockerContainer.False;
}
}
private static bool ReadProcToDetectDockerInLinux()
{
return File
.ReadAllText("/proc/1/cgroup")
.Contains("/docker/");
}
}
internal enum IsDockerContainer
{
True,
False,
Unknown
}
}

View file

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.DotNet.Cli.Telemetry
{
internal interface IDockerContainerDetector
{
IsDockerContainer IsDockerContainer();
}
}

View file

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal interface IParseResultLogRule
{
List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult);
}
}

View file

@ -1,10 +1,9 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Microsoft.DotNet.Cli
namespace Microsoft.DotNet.Cli.Telemetry
{
public interface ITelemetry
{

View file

@ -0,0 +1,168 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Net.NetworkInformation;
using System.ComponentModel;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal static class MacAddressGetter
{
private const string MacRegex = @"(?:[a-z0-9]{2}[:\-]){5}[a-z0-9]{2}";
private const string ZeroRegex = @"(?:00[:\-]){5}00";
private const int ErrorFileNotFound = 0x2;
public static string GetMacAddress()
{
try
{
var shelloutput = GetShellOutMacAddressOutput();
if (shelloutput == null)
{
return null;
}
return ParseMACAddress(shelloutput);
}
catch (Win32Exception e)
{
if (e.NativeErrorCode == ErrorFileNotFound)
{
return GetMacAddressByNetworkInterface();
}
else
{
throw;
}
}
}
private static string ParseMACAddress(string shelloutput)
{
string macAddress = null;
foreach (Match match in Regex.Matches(shelloutput, MacRegex, RegexOptions.IgnoreCase))
{
if (!Regex.IsMatch(match.Value, ZeroRegex))
{
macAddress = match.Value;
break;
}
}
if (macAddress != null)
{
return macAddress;
}
return null;
}
private static string GetIpCommandOutput()
{
var ipResult = new ProcessStartInfo
{
FileName = "ip",
Arguments = "link",
UseShellExecute = false
}.ExecuteAndCaptureOutput(out string ipStdOut, out string ipStdErr);
if (ipResult == 0)
{
return ipStdOut;
}
else
{
return null;
}
}
private static string GetShellOutMacAddressOutput()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var result = new ProcessStartInfo
{
FileName = "getmac.exe",
UseShellExecute = false
}.ExecuteAndCaptureOutput(out string stdOut, out string stdErr);
if (result == 0)
{
return stdOut;
}
else
{
return null;
}
}
else
{
try
{
var ifconfigResult = new ProcessStartInfo
{
FileName = "ifconfig",
Arguments = "-a",
UseShellExecute = false
}.ExecuteAndCaptureOutput(out string ifconfigStdOut, out string ifconfigStdErr);
if (ifconfigResult == 0)
{
return ifconfigStdOut;
}
else
{
return GetIpCommandOutput();
}
}
catch (Win32Exception e)
{
if (e.NativeErrorCode == ErrorFileNotFound)
{
return GetIpCommandOutput();
}
else
{
throw;
}
}
}
}
private static string GetMacAddressByNetworkInterface()
{
return GetMacAddressesByNetworkInterface().FirstOrDefault();
}
private static List<string> GetMacAddressesByNetworkInterface()
{
NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
var macs = new List<string>();
if (nics == null || nics.Length < 1)
{
macs.Add(string.Empty);
return macs;
}
foreach (NetworkInterface adapter in nics)
{
IPInterfaceProperties properties = adapter.GetIPProperties();
PhysicalAddress address = adapter.GetPhysicalAddress();
byte[] bytes = address.GetAddressBytes();
macs.Add(string.Join("-", bytes.Select(x => x.ToString("X2"))));
if (macs.Count >= 10)
{
break;
}
}
return macs;
}
}
}

View file

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal static class Sha256Hasher
{
/// <summary>
/// // The hashed mac address needs to be the same hashed value as produced by the other distinct sources given the same input. (e.g. VsCode)
/// </summary>
public static string Hash(string text)
{
var sha256 = SHA256.Create();
return HashInFormat(sha256, text);
}
private static string HashInFormat(SHA256 sha256, string text)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
byte[] hash = sha256.ComputeHash(bytes);
StringBuilder hashString = new StringBuilder();
foreach (byte x in hash)
{
hashString.AppendFormat("{0:x2}", x);
}
return hashString.ToString();
}
}
}

View file

@ -4,40 +4,32 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.PlatformAbstractions;
namespace Microsoft.DotNet.Cli
namespace Microsoft.DotNet.Cli.Telemetry
{
public class Telemetry : ITelemetry
{
internal static string CurrentSessionId = null;
private TelemetryClient _client = null;
private Dictionary<string, string> _commonProperties = null;
private Dictionary<string, double> _commonMeasurements = null;
private Task _trackEventTask = null;
private const string InstrumentationKey = "74cc1c9e-3e6e-4d05-b3fc-dde9101d0254";
private const string TelemetryOptout = "DOTNET_CLI_TELEMETRY_OPTOUT";
private const string TelemetryProfileEnvironmentVariable = "DOTNET_CLI_TELEMETRY_PROFILE";
private const string OSVersion = "OS Version";
private const string OSPlatform = "OS Platform";
private const string RuntimeId = "Runtime Id";
private const string ProductVersion = "Product Version";
private const string TelemetryProfile = "Telemetry Profile";
public bool Enabled { get; }
public Telemetry () : this(null) { }
public Telemetry() : this(null) { }
public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { }
public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId)
public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId, bool blockThreadInitialization = false)
{
Enabled = !Env.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel);
@ -49,8 +41,15 @@ namespace Microsoft.DotNet.Cli
// Store the session ID in a static field so that it can be reused
CurrentSessionId = sessionId ?? Guid.NewGuid().ToString();
//initialize in task to offload to parallel thread
_trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry());
if (blockThreadInitialization)
{
InitializeTelemetry();
}
else
{
//initialize in task to offload to parallel thread
_trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry());
}
}
private bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel)
@ -63,7 +62,8 @@ namespace Microsoft.DotNet.Cli
return sentinel.Exists();
}
public void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
public void TrackEvent(string eventName, IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
if (!Enabled)
{
@ -92,26 +92,23 @@ namespace Microsoft.DotNet.Cli
_client = new TelemetryClient();
_client.InstrumentationKey = InstrumentationKey;
_client.Context.Session.Id = CurrentSessionId;
_client.Context.Device.OperatingSystem = RuntimeEnvironment.OperatingSystem;
_commonProperties = new Dictionary<string, string>();
_commonProperties.Add(OSVersion, RuntimeEnvironment.OperatingSystemVersion);
_commonProperties.Add(OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString());
_commonProperties.Add(RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier());
_commonProperties.Add(ProductVersion, Product.Version);
_commonProperties.Add(TelemetryProfile, Environment.GetEnvironmentVariable(TelemetryProfileEnvironmentVariable));
_commonProperties = new TelemetryCommonProperties().GetTelemetryCommonProperties();
_commonMeasurements = new Dictionary<string, double>();
}
catch (Exception)
catch (Exception e)
{
_client = null;
// we dont want to fail the tool if telemetry fails.
Debug.Fail("Exception during telemetry initialization");
Debug.Fail(e.ToString());
}
}
private void TrackEventTask(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
private void TrackEventTask(
string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
if (_client == null)
{
@ -120,15 +117,15 @@ namespace Microsoft.DotNet.Cli
try
{
var eventProperties = GetEventProperties(properties);
var eventMeasurements = GetEventMeasures(measurements);
Dictionary<string, string> eventProperties = GetEventProperties(properties);
Dictionary<string, double> eventMeasurements = GetEventMeasures(measurements);
_client.TrackEvent(eventName, eventProperties, eventMeasurements);
_client.Flush();
}
catch (Exception)
catch (Exception e)
{
Debug.Fail("Exception during TrackEventTask");
Debug.Fail(e.ToString());
}
}
@ -137,7 +134,7 @@ namespace Microsoft.DotNet.Cli
Dictionary<string, double> eventMeasurements = new Dictionary<string, double>(_commonMeasurements);
if (measurements != null)
{
foreach (var measurement in measurements)
foreach (KeyValuePair<string, double> measurement in measurements)
{
if (eventMeasurements.ContainsKey(measurement.Key))
{
@ -157,7 +154,7 @@ namespace Microsoft.DotNet.Cli
if (properties != null)
{
var eventProperties = new Dictionary<string, string>(_commonProperties);
foreach (var property in properties)
foreach (KeyValuePair<string, string> property in properties)
{
if (eventProperties.ContainsKey(property.Key))
{

View file

@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.PlatformAbstractions;
using System.IO;
using Microsoft.DotNet.Configurer;
using System.Linq;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class TelemetryCommonProperties
{
public TelemetryCommonProperties(
Func<string> getCurrentDirectory = null,
Func<string, string> hasher = null,
Func<string> getMACAddress = null,
IDockerContainerDetector dockerContainerDetector = null,
IUserLevelCacheWriter userLevelCacheWriter = null)
{
_getCurrentDirectory = getCurrentDirectory ?? Directory.GetCurrentDirectory;
_hasher = hasher ?? Sha256Hasher.Hash;
_getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress;
_dockerContainerDetector = dockerContainerDetector ?? new DockerContainerDetectorForTelemetry();
_userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter(new CliFallbackFolderPathCalculator());
}
private readonly IDockerContainerDetector _dockerContainerDetector;
private Func<string> _getCurrentDirectory;
private Func<string, string> _hasher;
private Func<string> _getMACAddress;
private IUserLevelCacheWriter _userLevelCacheWriter;
private const string OSVersion = "OS Version";
private const string OSPlatform = "OS Platform";
private const string RuntimeId = "Runtime Id";
private const string ProductVersion = "Product Version";
private const string TelemetryProfile = "Telemetry Profile";
private const string CurrentPathHash = "Current Path Hash";
private const string MachineId = "Machine ID";
private const string DockerContainer = "Docker Container";
private const string TelemetryProfileEnvironmentVariable = "DOTNET_CLI_TELEMETRY_PROFILE";
private const string CannotFindMacAddress = "Unknown";
private const string MachineIdCacheKey = "MachineId";
private const string IsDockerContainerCacheKey = "IsDockerContainer";
public Dictionary<string, string> GetTelemetryCommonProperties()
{
return new Dictionary<string, string>
{
{OSVersion, RuntimeEnvironment.OperatingSystemVersion},
{OSPlatform, RuntimeEnvironment.OperatingSystemPlatform.ToString()},
{RuntimeId, RuntimeEnvironment.GetRuntimeIdentifier()},
{ProductVersion, Product.Version},
{TelemetryProfile, Environment.GetEnvironmentVariable(TelemetryProfileEnvironmentVariable)},
{DockerContainer, _userLevelCacheWriter.RunWithCache(IsDockerContainerCacheKey, () => _dockerContainerDetector.IsDockerContainer().ToString("G") )},
{CurrentPathHash, _hasher(_getCurrentDirectory())},
{MachineId, _userLevelCacheWriter.RunWithCache(MachineIdCacheKey, GetMachineId)}
};
}
private string GetMachineId()
{
var macAddress = _getMACAddress();
if (macAddress != null)
{
return _hasher(macAddress);
}
else
{
return Guid.NewGuid().ToString();
}
}
}
}

View file

@ -0,0 +1,95 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class TelemetryFilter : ITelemetryFilter
{
private const string DotnetName = "dotnet";
public IEnumerable<ApplicationInsightsEntryFormat> Filter(object objectToFilter)
{
var ruleSet = new List<IParseResultLogRule>
{ new AllowListToSendFirstArgument(new HashSet<string>{ "new", "help" }),
new AllowListToSendFirstAppliedOptions(new HashSet<string>{ "add", "remove", "list", "sln", "nuget" }),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "new" },
optionsToLog: new HashSet<string> { "language" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "build", "publish" },
optionsToLog: new HashSet<string> { "framework", "runtime", "configuration" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "run", "clean", "test" },
optionsToLog: new HashSet<string> { "framework", "configuration" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "pack" },
optionsToLog: new HashSet<string> { "configuration" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "migrate" },
optionsToLog: new HashSet<string> { "sdk-package-version" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "vstest" },
optionsToLog: new HashSet<string> { "platform", "framework", "logger" }
),
new TopLevelCommandNameAndOptionToLog
(
topLevelCommandName: new HashSet<string> { "publish" },
optionsToLog: new HashSet<string> { "runtime" }
)
};
var result = new List<ApplicationInsightsEntryFormat>();
if (objectToFilter is ParseResult parseResult)
{
var topLevelCommandName = parseResult[DotnetName]?.AppliedOptions?.FirstOrDefault()?.Name;
if (topLevelCommandName != null)
{
LogVerbosityForAllTopLevelCommand(result, parseResult, topLevelCommandName);
foreach (IParseResultLogRule rule in ruleSet)
{
result.AddRange(rule.AllowList(parseResult));
}
}
}
return result;
}
private static void LogVerbosityForAllTopLevelCommand(
ICollection<ApplicationInsightsEntryFormat> result,
ParseResult parseResult,
string topLevelCommandName)
{
if (parseResult[DotnetName][topLevelCommandName]?.AppliedOptions != null &&
parseResult[DotnetName][topLevelCommandName].AppliedOptions.Contains("verbosity"))
{
AppliedOption appliedOptions =
parseResult[DotnetName][topLevelCommandName].AppliedOptions["verbosity"];
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandName,
new Dictionary<string, string>()
{
{"verbosity", appliedOptions.Arguments.ElementAt(0)}
}));
}
}
}
}

View file

@ -0,0 +1,49 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Telemetry
{
internal class TopLevelCommandNameAndOptionToLog : IParseResultLogRule
{
public TopLevelCommandNameAndOptionToLog(
HashSet<string> topLevelCommandName,
HashSet<string> optionsToLog)
{
_topLevelCommandName = topLevelCommandName;
_optionsToLog = optionsToLog;
}
private HashSet<string> _topLevelCommandName { get; }
private HashSet<string> _optionsToLog { get; }
private const string DotnetName = "dotnet";
public List<ApplicationInsightsEntryFormat> AllowList(ParseResult parseResult)
{
var topLevelCommandName = parseResult[DotnetName]?.AppliedOptions?.FirstOrDefault()?.Name;
var result = new List<ApplicationInsightsEntryFormat>();
foreach (var option in _optionsToLog)
{
if (_topLevelCommandName.Contains(topLevelCommandName)
&& parseResult[DotnetName]?[topLevelCommandName]?.AppliedOptions != null
&& parseResult[DotnetName][topLevelCommandName].AppliedOptions.Contains(option))
{
AppliedOption appliedOptions =
parseResult[DotnetName][topLevelCommandName]
.AppliedOptions[option];
result.Add(new ApplicationInsightsEntryFormat(
"dotnet-" + topLevelCommandName,
new Dictionary<string, string>
{
{option, appliedOptions.Arguments.ElementAt(0)}
}));
}
}
return result;
}
}
}

View file

@ -6,6 +6,7 @@ using System.Linq;
using System.IO;
using System.Collections.Generic;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.Cli.Telemetry;
namespace Microsoft.DotNet.Cli
{
@ -37,13 +38,13 @@ namespace Microsoft.DotNet.Cli
internal class ThreadBlockingTelemetry : ITelemetry
{
private Telemetry telemetry;
private Telemetry.Telemetry telemetry;
internal ThreadBlockingTelemetry()
{
var sessionId =
Environment.GetEnvironmentVariable(TelemetrySessionIdEnvironmentVariableName);
telemetry = new Telemetry(new FirstTimeUseNoticeSentinel(new CliFallbackFolderPathCalculator()), sessionId);
telemetry = new Telemetry.Telemetry(new NoOpFirstTimeUseNoticeSentinel(), sessionId, blockThreadInitialization: true);
}
public bool Enabled => telemetry.Enabled;

View file

@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.CommandLine;
using System.Diagnostics;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.MSBuild

View file

@ -5,6 +5,7 @@ using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Configurer;
namespace Microsoft.DotNet.Tools.MSBuild
@ -39,15 +40,13 @@ namespace Microsoft.DotNet.Tools.MSBuild
{
if (_telemetry != null && _telemetry.Enabled)
{
IEventSource2 eventSource2 = eventSource as IEventSource2;
if (eventSource2 != null)
if (eventSource is IEventSource2 eventSource2)
{
eventSource2.TelemetryLogged += OnTelemetryLogged;
}
}
}
catch(Exception)
catch (Exception)
{
// Exceptions during telemetry shouldn't cause anything else to fail
}

View file

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.Tools.MSBuild;

View file

@ -32,7 +32,7 @@ namespace Microsoft.DotNet.Tools.NuGet
private class NuGetCommandRunner : ICommandRunner
{
public int Run(string [] args)
public int Run(string[] args)
{
var nugetApp = new NuGetForwardingApp(args);

View file

@ -233,5 +233,73 @@ namespace Microsoft.DotNet.Configurer.UnitTests
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Once);
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
}
[Fact]
public void It_prints_the_first_time_use_notice_if_the_cache_sentinel_exists_but_the_first_notice_sentinel_does_not()
{
_nugetCacheSentinelMock.Setup(n => n.Exists()).Returns(true);
_firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object,
CliFallbackFolderPath);
dotnetFirstTimeUseConfigurer.Configure();
_reporterMock.Verify(r =>
r.WriteLine(It.Is<string>(str => str == LocalizableStrings.FirstTimeWelcomeMessage)));
_reporterMock.Verify(
r => r.WriteLine(It.Is<string>(str => str == LocalizableStrings.NugetCachePrimeMessage)),
Times.Never);
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
}
[Fact]
public void It_prints_the_unauthorized_notice_if_the_cache_sentinel_reports_Unauthorized()
{
_nugetCacheSentinelMock.Setup(n => n.UnauthorizedAccess).Returns(true);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object,
CliFallbackFolderPath);
dotnetFirstTimeUseConfigurer.Configure();
_reporterMock.Verify(r =>
r.WriteLine(It.Is<string>(str => str == LocalizableStrings.FirstTimeWelcomeMessage)));
_reporterMock.Verify(r =>
r.WriteLine(It.Is<string>(str =>
str == string.Format(LocalizableStrings.UnauthorizedAccessMessage, CliFallbackFolderPath))));
_reporterMock.Verify(
r => r.WriteLine(It.Is<string>(str => str == LocalizableStrings.NugetCachePrimeMessage)),
Times.Never);
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
}
[Fact]
public void It_does_not_prime_the_cache_if_the_cache_sentinel_reports_Unauthorized()
{
_nugetCacheSentinelMock.Setup(n => n.UnauthorizedAccess).Returns(true);
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
_nugetCachePrimerMock.Object,
_nugetCacheSentinelMock.Object,
_firstTimeUseNoticeSentinelMock.Object,
_environmentProviderMock.Object,
_reporterMock.Object,
CliFallbackFolderPath);
dotnetFirstTimeUseConfigurer.Configure();
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Never);
}
}
}

View file

@ -0,0 +1,165 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FluentAssertions;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.Extensions.DependencyModel.Tests;
using Microsoft.Extensions.EnvironmentAbstractions;
using Moq;
using Xunit;
namespace Microsoft.DotNet.Configurer.UnitTests
{
public class GivenAFunctionReturnStringAndFakeFileSystem
{
private const string DOTNET_USER_PROFILE_FOLDER_PATH = "some path";
private FileSystemMockBuilder _fileSystemMockBuilder;
private UserLevelCacheWriter _userLevelCacheWriter;
private IFileSystem _fileSystemMock;
public GivenAFunctionReturnStringAndFakeFileSystem()
{
_fileSystemMockBuilder = FileSystemMockBuilder.Create();
_fileSystemMock = _fileSystemMockBuilder.Build();
_userLevelCacheWriter =
new UserLevelCacheWriter(
DOTNET_USER_PROFILE_FOLDER_PATH,
_fileSystemMock.File,
_fileSystemMock.Directory);
}
[Fact]
public void ItReturnsTheFunctionResult()
{
_userLevelCacheWriter.RunWithCache("fooKey", () => "foo").Should().Be("foo");
}
[Fact]
public void ItRunsTheFunctionOnlyOnceWhenInvokeTwice()
{
var counter = new Counter();
Func<string> func = () =>
{
counter.Increase();
return "foo";
};
_userLevelCacheWriter.RunWithCache("fookey", func).Should().Be("foo");
_userLevelCacheWriter.RunWithCache("fookey", func).Should().Be("foo");
counter.Count.Should().Be(1);
}
[Fact]
public void ItKeepsTheCacheInUserProfileWithCacheKey()
{
_userLevelCacheWriter.RunWithCache("fooKey", () => "foo");
var path = Path.Combine("some path", $"{Product.Version}_fooKey.dotnetUserLevelCache");
_fileSystemMock.File.Exists(path);
_fileSystemMock.File.ReadAllText(path).Should().Be("foo");
}
[Fact]
public void ItRunsAndReturnsTheValueIfCacheCreationFailed()
{
var mockFile = new Mock<IFile>();
var systemUndertest =
new UserLevelCacheWriter(
DOTNET_USER_PROFILE_FOLDER_PATH,
new NoPermissionFileFake(),
new NoPermissionDirectoryFake());
var counter = new Counter();
Func<string> func = () =>
{
counter.Increase();
return "foo";
};
systemUndertest.RunWithCache("fookey", func).Should().Be("foo");
systemUndertest.RunWithCache("fookey", func).Should().Be("foo");
counter.Count.Should().Be(2);
}
private class NoPermissionFileFake : IFile
{
public bool Exists(string path)
{
return false;
}
public string ReadAllText(string path)
{
throw new UnauthorizedAccessException();
}
public Stream OpenRead(string path)
{
throw new UnauthorizedAccessException();
}
public Stream OpenFile(
string path,
FileMode fileMode,
FileAccess fileAccess,
FileShare fileShare,
int bufferSize,
FileOptions fileOptions)
{
throw new NotImplementedException();
}
public void CreateEmptyFile(string path)
{
throw new UnauthorizedAccessException();
}
public void WriteAllText(string path, string content)
{
throw new UnauthorizedAccessException();
}
}
private class NoPermissionDirectoryFake : IDirectory
{
public ITemporaryDirectory CreateTemporaryDirectory()
{
throw new NotImplementedException();
}
public IEnumerable<string> GetFiles(string path, string searchPattern)
{
throw new UnauthorizedAccessException();
}
public string GetDirectoryFullName(string path)
{
throw new NotImplementedException();
}
public bool Exists(string path)
{
return false;
}
public void CreateDirectory(string path)
{
throw new UnauthorizedAccessException();
}
}
private class Counter
{
public int Count { get; private set; }
public void Increase() { Count++; }
}
}
}

View file

@ -70,6 +70,18 @@ namespace Microsoft.DotNet.Configurer.UnitTests
nugetCacheSentinel.InProgressSentinelAlreadyExists().Should().BeTrue();
}
[Fact]
public void It_returns_false_to_the_in_progress_sentinel_already_exists_when_it_fails_to_get_a_handle_to_it_but_it_failed_because_it_was_unauthorized()
{
var fileMock = new FileMock();
var directoryMock = new DirectoryMock();
fileMock.InProgressSentinel = null;
var nugetCacheSentinel =
new NuGetCacheSentinel(NUGET_CACHE_PATH, fileMock, directoryMock);
nugetCacheSentinel.InProgressSentinelAlreadyExists().Should().BeFalse();
}
[Fact]
public void It_returns_false_to_the_in_progress_sentinel_already_exists_when_it_succeeds_in_getting_a_handle_to_it()
{

View file

@ -18,6 +18,7 @@ using MSBuildCommand = Microsoft.DotNet.Tools.Test.Utilities.MSBuildCommand;
using System.Diagnostics;
using System.Threading;
// There are tests which modify static Telemetry.CurrentSessionId and they cannot run in parallel
[assembly: CollectionBehavior(DisableTestParallelization = true)]
@ -129,7 +130,7 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests
[Fact]
public void WhenTelemetryIsEnabledTheLoggerIsAddedToTheCommandLine()
{
Telemetry telemetry;
Telemetry.Telemetry telemetry;
string[] allArgs = GetArgsForMSBuild(() => true, out telemetry);
// telemetry will still be disabled if environment variable is set
if (telemetry.Enabled)
@ -156,14 +157,15 @@ namespace Microsoft.DotNet.Cli.MSBuild.Tests
private string[] GetArgsForMSBuild(Func<bool> sentinelExists)
{
Telemetry telemetry;
Telemetry.Telemetry telemetry;
return GetArgsForMSBuild(sentinelExists, out telemetry);
}
private string[] GetArgsForMSBuild(Func<bool> sentinelExists, out Telemetry telemetry)
private string[] GetArgsForMSBuild(Func<bool> sentinelExists, out Telemetry.Telemetry telemetry)
{
Telemetry.CurrentSessionId = null; // reset static session id modified by telemetry constructor
telemetry = new Telemetry(new MockNuGetCacheSentinel(sentinelExists));
Telemetry.Telemetry.CurrentSessionId = null; // reset static session id modified by telemetry constructor
telemetry = new Telemetry.Telemetry(new MockNuGetCacheSentinel(sentinelExists));
MSBuildForwardingApp msBuildForwardingApp = new MSBuildForwardingApp(Enumerable.Empty<string>());

View file

@ -42,8 +42,7 @@ namespace Microsoft.DotNet.New.Tests
bool skipSpaWebpackSteps)
{
string rootPath = TestAssets.CreateTestDirectory(identifier: $"{language}_{projectType}").FullName;
//This works around the SPA templates not currently supporting the "--no-restore" switch
string noRestoreDirective = skipSpaWebpackSteps ? "" : "--no-restore";
string noRestoreDirective = "--no-restore";
new TestCommand("dotnet") { WorkingDirectory = rootPath }
.Execute($"new {projectType} -lang {language} -o {rootPath} --debug:ephemeral-hive {noRestoreDirective}")

View file

@ -0,0 +1,39 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
namespace Microsoft.DotNet.Tests
{
public class FakeRecordEventNameTelemetry : ITelemetry
{
public bool Enabled { get; set; }
public string EventName { get; set; }
public void TrackEvent(string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
LogEntries.Add(
new LogEntry
{
EventName = eventName,
Measurement = measurements,
Properties = properties
});
}
public ConcurrentBag<LogEntry> LogEntries { get; set; } = new ConcurrentBag<LogEntry>();
public class LogEntry
{
public string EventName { get; set; }
public IDictionary<string, string> Properties { get; set; }
public IDictionary<string, double> Measurement { get; set; }
}
}
}

View file

@ -20,15 +20,17 @@ namespace Microsoft.DotNet.Tests
private static CommandResult _firstDotnetNonVerbUseCommandResult;
private static CommandResult _firstDotnetVerbUseCommandResult;
private static DirectoryInfo _nugetFallbackFolder;
private static DirectoryInfo _dotDotnetFolder;
private static string _testDirectory;
static GivenThatTheUserIsRunningDotNetForTheFirstTime()
{
var testDirectory = TestAssets.CreateTestDirectory("Dotnet_first_time_experience_tests");
var testNuGetHome = Path.Combine(testDirectory.FullName, "nuget_home");
_testDirectory = TestAssets.CreateTestDirectory("Dotnet_first_time_experience_tests").FullName;
var testNuGetHome = Path.Combine(_testDirectory, "nuget_home");
var cliTestFallbackFolder = Path.Combine(testNuGetHome, ".dotnet", "NuGetFallbackFolder");
var command = new DotnetCommand()
.WithWorkingDirectory(testDirectory);
.WithWorkingDirectory(_testDirectory);
command.Environment["HOME"] = testNuGetHome;
command.Environment["USERPROFILE"] = testNuGetHome;
command.Environment["APPDATA"] = testNuGetHome;
@ -40,6 +42,7 @@ namespace Microsoft.DotNet.Tests
_firstDotnetVerbUseCommandResult = command.ExecuteWithCapturedOutput("new --debug:ephemeral-hive");
_nugetFallbackFolder = new DirectoryInfo(cliTestFallbackFolder);
_dotDotnetFolder = new DirectoryInfo(Path.Combine(testNuGetHome, ".dotnet"));
}
[Fact]
@ -77,6 +80,61 @@ namespace Microsoft.DotNet.Tests
.HaveFile($"{GetDotnetVersion()}.dotnetSentinel");
}
[Fact]
public void ItCreatesAFirstUseSentinelFileUnderTheDotDotNetFolder()
{
_dotDotnetFolder
.Should()
.HaveFile($"{GetDotnetVersion()}.dotnetFirstUseSentinel");
}
[Fact]
public void ItDoesNotCreateAFirstUseSentinelFileUnderTheDotDotNetFolderWhenInternalReportInstallSuccessIsInvoked()
{
var emptyHome = Path.Combine(_testDirectory, "empty_home");
var command = new DotnetCommand()
.WithWorkingDirectory(_testDirectory);
command.Environment["HOME"] = emptyHome;
command.Environment["USERPROFILE"] = emptyHome;
command.Environment["APPDATA"] = emptyHome;
command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName;
command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "";
// Disable to prevent the creation of the .dotnet folder by optimizationdata.
command.Environment["DOTNET_DISABLE_MULTICOREJIT"] = "true";
command.Environment["SkipInvalidConfigurations"] = "true";
command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass();
var homeFolder = new DirectoryInfo(Path.Combine(emptyHome, ".dotnet"));
string[] fileEntries = Directory.GetFiles(homeFolder.ToString());
fileEntries.Should().OnlyContain(x => !x.Contains(".dotnetFirstUseSentinel"));
}
[Fact]
public void ItShowsTheTelemetryNoticeWhenInvokingACommandAfterInternalReportInstallSuccessHasBeenInvoked()
{
var newHome = Path.Combine(_testDirectory, "new_home");
var newHomeFolder = new DirectoryInfo(Path.Combine(newHome, ".dotnet"));
var command = new DotnetCommand()
.WithWorkingDirectory(_testDirectory);
command.Environment["HOME"] = newHome;
command.Environment["USERPROFILE"] = newHome;
command.Environment["APPDATA"] = newHome;
command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName;
command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "";
command.Environment["SkipInvalidConfigurations"] = "true";
command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass();
var result = command.ExecuteWithCapturedOutput("new --debug:ephemeral-hive");
result.StdOut
.Should()
.ContainVisuallySameFragment(Configurer.LocalizableStrings.FirstTimeWelcomeMessage);
}
[Fact]
public void ItRestoresTheNuGetPackagesToTheNuGetCacheFolder()
{

View file

@ -1,52 +1,229 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit;
using FluentAssertions;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Tools.Test.Utilities;
using System.Collections.Generic;
using Xunit;
namespace Microsoft.DotNet.Tests
{
public class MockTelemetry : ITelemetry
public class TelemetryCommandTests : TestBase
{
public bool Enabled { get; set; }
private readonly FakeRecordEventNameTelemetry _fakeTelemetry;
public string EventName { get; set; }
public IDictionary<string, string> Properties { get; set; }
public void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
public TelemetryCommandTests()
{
EventName = eventName;
Properties = properties;
_fakeTelemetry = new FakeRecordEventNameTelemetry();
TelemetryEventEntry.Subscribe(_fakeTelemetry.TrackEvent);
TelemetryEventEntry.TelemetryFilter = new TelemetryFilter();
}
}
public class TelemetryCommandTests : TestBase
{
[Fact]
public void TestProjectDependencyIsNotAvailableThroughDriver()
public void TopLevelCommandNameShouldBeSentToTelemetry()
{
MockTelemetry mockTelemetry = new MockTelemetry();
string[] args = { "help" };
Microsoft.DotNet.Cli.Program.ProcessArgs(args, mockTelemetry);
Assert.Equal(mockTelemetry.EventName, args[0]);
string[] args = {"help"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == args[0]);
}
[Fact]
public void DotnetNewCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "console";
string[] args = {"new", argumentToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-new" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetHelpCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "something";
string[] args = {"help", argumentToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-help" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetAddCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "package";
string[] args = {"add", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-add" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetAddCommandFirstArgumentShouldBeSentToTelemetry2()
{
const string argumentToSend = "reference";
string[] args = {"add", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-add" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetRemoveCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "package";
string[] args = {"remove", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-remove" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetListCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "reference";
string[] args = {"list", argumentToSend, "aPackageName"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-list" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetSlnCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "list";
string[] args = {"sln", "aSolution", argumentToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-sln" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetNugetCommandFirstArgumentShouldBeSentToTelemetry()
{
const string argumentToSend = "push";
string[] args = {"nuget", argumentToSend, "aRoot"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-nuget" && e.Properties.ContainsKey("argument") &&
e.Properties["argument"] == argumentToSend);
}
[Fact]
public void DotnetNewCommandLanguageOpinionShouldBeSentToTelemetry()
{
const string optionKey = "language";
const string optionValueToSend = "c#";
string[] args = {"new", "console", "--" + optionKey, optionValueToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-new" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void AnyDotnetCommandVerbosityOpinionShouldBeSentToTelemetry()
{
const string optionKey = "verbosity";
const string optionValueToSend = "minimal";
string[] args = {"restore", "--" + optionKey, optionValueToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-restore" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void DotnetBuildAndPublishCommandOpinionsShouldBeSentToTelemetry()
{
const string optionKey = "configuration";
const string optionValueToSend = "Debug";
string[] args = {"build", "--" + optionKey, optionValueToSend};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-build" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void DotnetPublishCommandRuntimeOpinionsShouldBeSentToTelemetry()
{
const string optionKey = "runtime";
const string optionValueToSend = "win10-x64";
string[] args = { "publish", "--" + optionKey, optionValueToSend };
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-publish" && e.Properties.ContainsKey(optionKey) &&
e.Properties[optionKey] == optionValueToSend);
}
[Fact]
public void DotnetBuildAndPublishCommandOpinionsShouldBeSentToTelemetryWhenThereIsMultipleOption()
{
string[] args = {"build", "--configuration", "Debug", "--runtime", "osx.10.11-x64"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-build" && e.Properties.ContainsKey("configuration") &&
e.Properties["configuration"] == "Debug");
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-build" && e.Properties.ContainsKey("runtime") &&
e.Properties["runtime"] == "osx.10.11-x64");
}
[Fact]
public void DotnetRunCleanTestCommandOpinionsShouldBeSentToTelemetryWhenThereIsMultipleOption()
{
string[] args = {"clean", "--configuration", "Debug", "--framework", "netcoreapp1.0"};
Cli.Program.ProcessArgs(args);
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-clean" && e.Properties.ContainsKey("configuration") &&
e.Properties["configuration"] == "Debug");
_fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "dotnet-clean" && e.Properties.ContainsKey("framework") &&
e.Properties["framework"] == "netcoreapp1.0");
}
[WindowsOnlyFact]
public void InternalreportinstallsuccessCommandCollectExeNameWithEventname()
{
MockTelemetry mockTelemetry = new MockTelemetry();
FakeRecordEventNameTelemetry fakeTelemetry = new FakeRecordEventNameTelemetry();
string[] args = { "c:\\mypath\\dotnet-sdk-latest-win-x64.exe" };
InternalReportinstallsuccess.ProcessInputAndSendTelemetry(args, mockTelemetry);
InternalReportinstallsuccess.ProcessInputAndSendTelemetry(args, fakeTelemetry);
mockTelemetry.EventName.Should().Be("reportinstallsuccess");
mockTelemetry.Properties["exeName"].Should().Be("dotnet-sdk-latest-win-x64.exe");
fakeTelemetry
.LogEntries.Should()
.Contain(e => e.EventName == "reportinstallsuccess" && e.Properties.ContainsKey("exeName") &&
e.Properties["exeName"] == "dotnet-sdk-latest-win-x64.exe");
}
[Fact]

View file

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using FluentAssertions;
using Microsoft.DotNet.Tools.Test.Utilities;
using Xunit;
using System;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Configurer;
namespace Microsoft.DotNet.Tests
{
public class TelemetryCommonPropertiesTests : TestBase
{
[Fact]
public void TelemetryCommonPropertiesShouldContainIfItIsInDockerOrNot()
{
var unitUnderTest = new TelemetryCommonProperties(userLevelCacheWriter: new NothingCache());
unitUnderTest.GetTelemetryCommonProperties().Should().ContainKey("Docker Container");
}
[Fact]
public void TelemetryCommonPropertiesShouldReturnHashedPath()
{
var unitUnderTest = new TelemetryCommonProperties(() => "ADirectory", userLevelCacheWriter: new NothingCache());
unitUnderTest.GetTelemetryCommonProperties()["Current Path Hash"].Should().NotBe("ADirectory");
}
[Fact]
public void TelemetryCommonPropertiesShouldReturnHashedMachineId()
{
var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => "plaintext", userLevelCacheWriter: new NothingCache());
unitUnderTest.GetTelemetryCommonProperties()["Machine ID"].Should().NotBe("plaintext");
}
[Fact]
public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress()
{
var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
var assignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["Machine ID"];
Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid");
}
private class NothingCache : IUserLevelCacheWriter
{
public string RunWithCache(string cacheKey, Func<string> getValueToCache)
{
return getValueToCache();
}
}
}
}

View file

@ -0,0 +1,38 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.DotNet.Cli;
namespace Microsoft.DotNet.Cli.MSBuild.IntegrationTests
{
public class FakeRecordEventNameTelemetry
{
public bool Enabled { get; set; }
public string EventName { get; set; }
public void TrackEvent(string eventName,
IDictionary<string, string> properties,
IDictionary<string, double> measurements)
{
LogEntries.Add(
new LogEntry
{
EventName = eventName,
Measurement = measurements,
Properties = properties
});
}
public ConcurrentBag<LogEntry> LogEntries { get; set; } = new ConcurrentBag<LogEntry>();
public class LogEntry
{
public string EventName { get; set; }
public IDictionary<string, string> Properties { get; set; }
public IDictionary<string, double> Measurement { get; set; }
}
}
}