SingleFile: Add test cases (dotnet/core-setup#5954)
authorSwaroop Sridhar <Swaroop.Sridhar@microsoft.com>
Fri, 3 May 2019 19:19:38 +0000 (12:19 -0700)
committerGitHub <noreply@github.com>
Fri, 3 May 2019 19:19:38 +0000 (12:19 -0700)
SingleFile: Add test cases

Add test cases to test:
  * Run of self-contained and portable apps.
  * Extraction to custom directories.
  * Reuse of extracted files on subsequent runs.

Created a BundleTest test directory grouping which contains
- Microsoft.NET.HostModel.Bundle tests
- AppHost Bundler tests
- BundleHelpers -- containing common helper methods for the above two tests.

Commit migrated from https://github.com/dotnet/core-setup/commit/b0bd078ce1cc4a2791c2a09860f4e4cd71f9e17e

33 files changed:
Microsoft.DotNet.CoreSetup.sln
src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs
src/installer/managed/Microsoft.NET.HostModel/Bundle/Extractor.cs
src/installer/managed/Microsoft.NET.HostModel/Bundle/FileEntry.cs
src/installer/managed/Microsoft.NET.HostModel/Bundle/FileSpec.cs
src/installer/managed/Microsoft.NET.HostModel/Bundle/Manifest.cs
src/installer/test/Assets/TestProjects/AppWithSubDirs/AppWithSubDirs.csproj [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/StandaloneAppWithSubDirs.csproj with 89% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Program.cs [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Program.cs with 97% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Conjunction/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Conjunction/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Interjection/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Interjection/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Noun/Adjective/Article/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Noun/Adjective/Article/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Noun/Adjective/Preposition/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Noun/Adjective/Preposition/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Noun/Adjective/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Noun/Adjective/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Noun/Pronoun/Another/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Noun/Pronoun/Another/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Noun/Pronoun/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Noun/Pronoun/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Noun/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Noun/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/This is a really, really, really, really, really, really, really, really, really, really, really, really, really, really long file name for punctuation/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/This is a really, really, really, really, really, really, really, really, really, really, really, really, really, really long file name for punctuation/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Verb/Adverb/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Verb/Adverb/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/Verb/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/Verb/word with 100% similarity]
src/installer/test/Assets/TestProjects/AppWithSubDirs/Sentence/word [moved from src/installer/test/Assets/TestProjects/StandaloneAppWithSubDirs/Sentence/word with 100% similarity]
src/installer/test/BundleTests/AppHost.Bundle.Tests/AppHost.Bundle.Tests.csproj [moved from src/installer/test/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Tests.csproj with 55% similarity]
src/installer/test/BundleTests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs [new file with mode: 0644]
src/installer/test/BundleTests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs [new file with mode: 0644]
src/installer/test/BundleTests/Helpers/BundleHelper.cs [new file with mode: 0644]
src/installer/test/BundleTests/Helpers/BundleHelper.csproj [new file with mode: 0644]
src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/BundleExtractRun.cs [new file with mode: 0644]
src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs [new file with mode: 0644]
src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/Microsoft.NET.HostModel.Bundle.Tests.csproj [new file with mode: 0644]
src/installer/test/HostActivationTests/BundledAppWithSubDirs.cs [deleted file]
src/installer/test/Microsoft.NET.HostModel.Tests/BundleAndExtract.cs [deleted file]
src/installer/test/TestUtils/Assertions/DirectoryInfoAssertions.cs
src/installer/test/TestUtils/TestProjectFixture.cs
src/installer/test/dir.props

index c3a149b..00f5cc2 100644 (file)
@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 15
-VisualStudioVersion = 15.0.27527.1
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28803.352
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5A29E8E3-A0FC-4C57-81DD-297B56D1A119}"
        ProjectSection(SolutionItems) = preProject
@@ -25,7 +25,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtils", "src\test\TestU
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.HostModel", "src\managed\Microsoft.NET.HostModel\Microsoft.NET.HostModel.csproj", "{325FB7F2-2E2E-422D-ADAA-F0B63E84CF24}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.HostModel.Tests", "src\test\Microsoft.NET.HostModel.Tests\Microsoft.NET.HostModel.Tests.csproj", "{3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppHost.Bundle.Tests", "src\test\BundleTests\AppHost.Bundle.Tests\AppHost.Bundle.Tests.csproj", "{2745A51D-3425-4F68-8349-A8B8BC27DD87}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.HostModel.Bundle.Tests", "src\test\BundleTests\Microsoft.NET.HostModel.Bundle.Tests\Microsoft.NET.HostModel.Bundle.Tests.csproj", "{1E76A78E-9E39-480D-8CD3-B7D0A858FECB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BundleHelper", "src\test\BundleTests\Helpers\BundleHelper.csproj", "{8116F946-FB24-4524-9028-43F5A3A80EE3}"
 EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -135,22 +139,54 @@ Global
                {325FB7F2-2E2E-422D-ADAA-F0B63E84CF24}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
                {325FB7F2-2E2E-422D-ADAA-F0B63E84CF24}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
                {325FB7F2-2E2E-422D-ADAA-F0B63E84CF24}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Debug|x64.ActiveCfg = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Debug|x64.Build.0 = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.MinSizeRel|x64.Build.0 = Debug|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Release|Any CPU.Build.0 = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Release|x64.ActiveCfg = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.Release|x64.Build.0 = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Debug|x64.Build.0 = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.MinSizeRel|x64.Build.0 = Debug|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Release|Any CPU.Build.0 = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Release|x64.ActiveCfg = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.Release|x64.Build.0 = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Debug|x64.Build.0 = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.MinSizeRel|x64.Build.0 = Debug|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Release|Any CPU.Build.0 = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Release|x64.ActiveCfg = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.Release|x64.Build.0 = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Debug|x64.ActiveCfg = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Debug|x64.Build.0 = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.MinSizeRel|x64.Build.0 = Debug|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Release|Any CPU.Build.0 = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Release|x64.ActiveCfg = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.Release|x64.Build.0 = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
+               {8116F946-FB24-4524-9028-43F5A3A80EE3}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
@@ -162,7 +198,9 @@ Global
                {D86A859D-E6FA-4E73-A255-5776FC473A25} = {5CE8410C-3100-4F41-8FA9-E6B4132D9703}
                {D6676666-D14D-4DFA-88FB-76E3E823E2E1} = {5CE8410C-3100-4F41-8FA9-E6B4132D9703}
                {325FB7F2-2E2E-422D-ADAA-F0B63E84CF24} = {FAA448DA-7D1C-4481-915D-5765BF906332}
-               {3D07933E-8A4B-4C9A-92FD-473B5C4F71E2} = {5CE8410C-3100-4F41-8FA9-E6B4132D9703}
+               {2745A51D-3425-4F68-8349-A8B8BC27DD87} = {5CE8410C-3100-4F41-8FA9-E6B4132D9703}
+               {1E76A78E-9E39-480D-8CD3-B7D0A858FECB} = {5CE8410C-3100-4F41-8FA9-E6B4132D9703}
+               {8116F946-FB24-4524-9028-43F5A3A80EE3} = {5CE8410C-3100-4F41-8FA9-E6B4132D9703}
        EndGlobalSection
        GlobalSection(ExtensibilityGlobals) = postSolution
                SolutionGuid = {28B9726D-802B-478D-AF7A-B9243B9E180B}
index fb42c65..67a478f 100644 (file)
@@ -24,6 +24,7 @@ namespace Microsoft.NET.HostModel.Bundle
         readonly string RuntimeConfigDevJson;
 
         readonly Trace trace;
+        public readonly Manifest BundleManifest;
 
         /// <summary>
         /// Align embedded assemblies such that they can be loaded 
@@ -47,6 +48,7 @@ namespace Microsoft.NET.HostModel.Bundle
 
             EmbedPDBs = embedPDBs;
             trace = new Trace(diagnosticOutput);
+            BundleManifest = new Manifest();
         }
 
         /// <summary>
@@ -142,10 +144,9 @@ namespace Microsoft.NET.HostModel.Bundle
         {
             trace.Log($"Bundler version {Version}");
 
-            string bundlePath = Path.Combine(OutputDir, HostName);
-            if (File.Exists(bundlePath))
+            if (fileSpecs.Any(x => !x.IsValid()))
             {
-                trace.Log($"Ovewriting existing File {bundlePath}");
+                throw new ArgumentException("Invalid input specification: Found entry with empty source-path or bundle-relative-path.");
             }
 
             string hostSource;
@@ -158,13 +159,18 @@ namespace Microsoft.NET.HostModel.Bundle
                 throw new ArgumentException("Input must uniquely specify the host binary");
             }
 
+            string bundlePath = Path.Combine(OutputDir, HostName);
+            if (File.Exists(bundlePath))
+            {
+                trace.Log($"Ovewriting existing File {bundlePath}");
+            }
+
             // Start with a copy of the host executable.
             // Copy the file to preserve its permissions.
             File.Copy(hostSource, bundlePath, overwrite: true);
 
             using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(bundlePath)))
             {
-                Manifest manifest = new Manifest();
                 Stream bundle = writer.BaseStream;
                 bundle.Position = bundle.Length;
 
@@ -182,13 +188,13 @@ namespace Microsoft.NET.HostModel.Bundle
                         FileType type = InferType(fileSpec.BundleRelativePath, file);
                         long startOffset = AddToBundle(bundle, file, type);
                         FileEntry entry = new FileEntry(type, fileSpec.BundleRelativePath, startOffset, file.Length);
-                        manifest.Files.Add(entry);
+                        BundleManifest.Files.Add(entry);
                         trace.Log($"Embed: {entry}");
                     }
                 }
 
                 // Write the bundle manifest
-                long manifestOffset = manifest.Write(writer);
+                long manifestOffset = BundleManifest.Write(writer);
                 trace.Log($"Manifest: Offset={manifestOffset}, Size={writer.BaseStream.Position - manifestOffset}");
                 trace.Log($"Bundle: Path={bundlePath} Size={bundle.Length}");
             }
index 8193c95..fe5da74 100644 (file)
@@ -49,7 +49,7 @@ namespace Microsoft.NET.HostModel.Bundle
                     {
                         trace.Log($"Extract: {entry}");
 
-                        string fileRelativePath = entry.RelativePath.Replace(Manifest.DirectorySeparatorChar, Path.DirectorySeparatorChar);
+                        string fileRelativePath = entry.RelativePath.Replace(FileEntry.DirectorySeparatorChar, Path.DirectorySeparatorChar);
                         string filePath = Path.Combine(OutputDir, fileRelativePath);
                         string fileDir = Path.GetDirectoryName(filePath);
 
index 6480497..7939910 100644 (file)
@@ -20,15 +20,17 @@ namespace Microsoft.NET.HostModel.Bundle
     /// </summary>
     public class FileEntry
     {
-        public long Offset;
-        public long Size;
-        public FileType Type;
-        public string RelativePath; // Path of an embedded file, relative to the Bundle source-directory.
+        public readonly long Offset;
+        public readonly long Size;
+        public readonly FileType Type;
+        public readonly string RelativePath; // Path of an embedded file, relative to the Bundle source-directory.
+
+        public const char DirectorySeparatorChar = '/';
 
         public FileEntry(FileType fileType, string relativePath, long offset, long size)
         {
             Type = fileType;
-            RelativePath = relativePath.Replace(Path.DirectorySeparatorChar, Manifest.DirectorySeparatorChar);
+            RelativePath = relativePath.Replace(Path.DirectorySeparatorChar, DirectorySeparatorChar);
             Offset = offset;
             Size = size;
         }
index 7b18f57..79d933c 100644 (file)
@@ -15,14 +15,20 @@ namespace Microsoft.NET.HostModel.Bundle
     /// </summary>
     public struct FileSpec
     {
-        public string SourcePath;
-        public string BundleRelativePath;
+        public readonly string SourcePath;
+        public readonly string BundleRelativePath;
 
         public FileSpec(string sourcePath, string bundleRelativePath)
         {
             SourcePath = sourcePath;
             BundleRelativePath = bundleRelativePath;
         }
+
+        public bool IsValid()
+        {
+            return !string.IsNullOrWhiteSpace(SourcePath) && 
+                   !string.IsNullOrWhiteSpace(BundleRelativePath);
+        }
     }
 }
 
index 4571057..f24837a 100644 (file)
@@ -50,13 +50,12 @@ namespace Microsoft.NET.HostModel.Bundle
         public const string Signature = ".NetCoreBundle";
         public const uint MajorVersion = 0;
         public const uint MinorVersion = 1;
-        public const char DirectorySeparatorChar = '/';
 
         // Bundle ID is a string that is used to uniquely 
         // identify this bundle. It is choosen to be compatible
         // with path-names so that the AppHost can use it in
         // extraction path.
-        string BundleID;
+        public readonly string BundleID;
 
         public List<FileEntry> Files;
 
@@ -66,6 +65,12 @@ namespace Microsoft.NET.HostModel.Bundle
             BundleID = Path.GetRandomFileName();
         }
 
+        public Manifest(string bundleID)
+        {
+            Files = new List<FileEntry>();
+            BundleID = bundleID;
+        }
+
         public long Write(BinaryWriter writer)
         {
             long startOffset = writer.BaseStream.Position;
@@ -91,8 +96,6 @@ namespace Microsoft.NET.HostModel.Bundle
 
         public static Manifest Read(BinaryReader reader)
         {
-            Manifest manifest = new Manifest();
-
             // Read the manifest footer
 
             // signatureSize is one byte longer, for the length encoding.
@@ -113,7 +116,7 @@ namespace Microsoft.NET.HostModel.Bundle
             uint majorVersion = reader.ReadUInt32();
             uint minorVersion = reader.ReadUInt32();
             int fileCount = reader.ReadInt32();
-            manifest.BundleID = reader.ReadString(); // Bundle ID
+            string bundleID = reader.ReadString(); // Bundle ID
 
             if (majorVersion != MajorVersion || minorVersion != MinorVersion)
             {
@@ -121,6 +124,7 @@ namespace Microsoft.NET.HostModel.Bundle
             }
 
             // Read the manifest entries
+            Manifest manifest = new Manifest(bundleID);
             for (long i = 0; i < fileCount; i++)
             {
                 manifest.Files.Add(FileEntry.Read(reader));
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>$(NETCoreAppFramework)</TargetFramework>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
     <OutputType>Exe</OutputType>
     <RuntimeIdentifier>$(TestTargetRid)</RuntimeIdentifier>
     <RuntimeFrameworkVersion>$(MNAVersion)</RuntimeFrameworkVersion>
@@ -11,7 +11,7 @@
     <ItemGroup>
       <FilesToCopy Include="$(MSBuildThisFileDirectory)\Sentence\**\*.*" />
     </ItemGroup>
-    
+
     <Copy SourceFiles="@(FilesToCopy)" DestinationFolder="$(PublishDir)\Sentence\%(RecursiveDir)" SkipUnchangedFiles="true"/>
   </Target>
 
@@ -1,22 +1,23 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <Description>Microsoft.NET.HostModel.Tests</Description>
+    <Description>Apphost Bundle Tests</Description>
     <TargetFramework>netcoreapp2.0</TargetFramework>
-    <AssemblyName>Microsoft.NET.HostModel.Tests</AssemblyName>
-    <PackageId>Microsoft.NET.HostModel.Tests</PackageId>
+    <AssemblyName>AppHost.Bundle.Tests</AssemblyName>
+    <PackageId>AppHost.Bundle.Tests</PackageId>
     <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
   </PropertyGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\TestUtils\TestUtils.csproj" />
+    <ProjectReference Include="..\..\TestUtils\TestUtils.csproj" />
+    <ProjectReference Include="..\..\..\managed\Microsoft.NET.HostModel\Microsoft.NET.HostModel.csproj" />
+    <ProjectReference Include="..\Helpers\BundleHelper.csproj" />
   </ItemGroup>
 
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
     <PackageReference Include="xunit" Version="2.2.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
-    <ProjectReference Include="..\..\managed\Microsoft.NET.HostModel\Microsoft.NET.HostModel.csproj" />
   </ItemGroup>
 
 </Project>
diff --git a/src/installer/test/BundleTests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs b/src/installer/test/BundleTests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs
new file mode 100644 (file)
index 0000000..5812d2d
--- /dev/null
@@ -0,0 +1,143 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Threading;
+using Xunit;
+using Microsoft.DotNet.Cli.Build.Framework;
+using Microsoft.DotNet.CoreSetup.Test;
+using BundleTests.Helpers;
+
+namespace AppHost.Bundle.Tests
+{
+    public class BundleExtractToSpecificPath : IClassFixture<BundleExtractToSpecificPath.SharedTestState>
+    {
+        private SharedTestState sharedTestState;
+
+        public BundleExtractToSpecificPath(SharedTestState fixture)
+        {
+            sharedTestState = fixture;
+        }
+
+        [Fact]
+        private void Bundle_Extraction_To_Specific_Path_Succeeds()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+            var hostName = BundleHelper.GetHostName(fixture);
+            var appName = Path.GetFileNameWithoutExtension(hostName);
+            string publishPath = BundleHelper.GetPublishPath(fixture);
+
+            // Publish the bundle
+            var bundleDir = BundleHelper.GetBundleDir(fixture);
+            var bundler = new Microsoft.NET.HostModel.Bundle.Bundler(hostName, bundleDir.FullName);
+            string singleFile = bundler.GenerateBundle(publishPath);
+
+            // Compute bundled files
+            var bundledFiles = bundler.BundleManifest.Files.Select(file => file.RelativePath).ToList();
+
+            // Verify expected files in the bundle directory
+            bundleDir.Should().HaveFile(hostName);
+            bundleDir.Should().NotHaveFiles(bundledFiles);
+
+            // Create a directory for extraction.
+            var extractBaseDir = BundleHelper.GetExtractDir(fixture);
+            extractBaseDir.Should().NotHaveDirectory(appName);
+
+            // Run the bundled app for the first time, and extract files to 
+            // $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/bundle-id
+            Command.Create(singleFile)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .EnvironmentVariable(BundleHelper.DotnetBundleExtractBaseEnvVariable, extractBaseDir.FullName)
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdOutContaining("Hello World");
+
+            string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID);
+            var extractDir = new DirectoryInfo(extractPath);
+            extractDir.Should().OnlyHaveFiles(bundledFiles);
+            extractDir.Should().NotHaveFile(hostName);
+        }
+
+        [Fact]
+        private void Bundle_extraction_is_reused()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+            var hostName = BundleHelper.GetHostName(fixture);
+            var appName = Path.GetFileNameWithoutExtension(hostName);
+            string publishPath = BundleHelper.GetPublishPath(fixture);
+
+            // Publish the bundle
+            var bundleDir = BundleHelper.GetBundleDir(fixture);
+            var bundler = new Microsoft.NET.HostModel.Bundle.Bundler(hostName, bundleDir.FullName);
+            string singleFile = bundler.GenerateBundle(publishPath);
+
+            // Create a directory for extraction.
+            var extractBaseDir = BundleHelper.GetExtractDir(fixture);
+
+            // Run the bunded app for the first time, and extract files to 
+            // $DOTNET_BUNDLE_EXTRACT_BASE_DIR/<app>/bundle-id
+            Command.Create(singleFile)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .EnvironmentVariable(BundleHelper.DotnetBundleExtractBaseEnvVariable, extractBaseDir.FullName)
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdOutContaining("Hello World");
+
+            string extractPath = Path.Combine(extractBaseDir.FullName, appName, bundler.BundleManifest.BundleID);
+            var extractDir = new DirectoryInfo(extractPath);
+
+            extractDir.Refresh();
+            DateTime firstWriteTime = extractDir.LastWriteTimeUtc;
+
+            while (DateTime.Now == firstWriteTime)
+            {
+                Thread.Sleep(1);
+            }
+
+            // Run the bundled app again (reuse extracted files)
+            Command.Create(singleFile)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .EnvironmentVariable(BundleHelper.DotnetBundleExtractBaseEnvVariable, extractBaseDir.FullName)
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdOutContaining("Hello World");
+
+            extractDir.Should().NotBeModifiedAfter(firstWriteTime);
+        }
+
+
+        public class SharedTestState : IDisposable
+        {
+            public TestProjectFixture TestFixture { get; set; }
+            public RepoDirectoriesProvider RepoDirectories { get; set; }
+
+            public SharedTestState()
+            {
+                RepoDirectories = new RepoDirectoriesProvider();
+                TestFixture = new TestProjectFixture("StandaloneApp", RepoDirectories);
+                TestFixture
+                    .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
+                    .PublishProject(runtime: TestFixture.CurrentRid, 
+                                    outputDirectory: BundleHelper.GetPublishPath(TestFixture));
+            }
+
+            public void Dispose()
+            {
+                TestFixture.Dispose();
+            }
+        }
+    }
+}
diff --git a/src/installer/test/BundleTests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs b/src/installer/test/BundleTests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs
new file mode 100644 (file)
index 0000000..0ca66e5
--- /dev/null
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Xunit;
+using Microsoft.DotNet.Cli.Build.Framework;
+using BundleTests.Helpers;
+using Microsoft.DotNet.CoreSetup.Test;
+
+namespace AppHost.Bundle.Tests
+{
+    public class BundledAppWithSubDirs : IClassFixture<BundledAppWithSubDirs.SharedTestState>
+    {
+        private SharedTestState sharedTestState;
+
+        public BundledAppWithSubDirs(SharedTestState fixture)
+        {
+            sharedTestState = fixture;
+        }
+
+        private void RunTheApp(string path)
+        {
+            Command.Create(path)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdOutContaining("Wow! We now say hello to the big world and you.");
+        }
+
+        [Fact]
+        private void Bundled_Framework_dependent_App_Run_Succeeds()
+        {
+            var fixture = sharedTestState.TestFrameworkDependentFixture.Copy();
+            var singleFile = BundleHelper.BundleApp(fixture);
+
+            // Run the bundled app (extract files)
+            RunTheApp(singleFile);
+
+            // Run the bundled app again (reuse extracted files)
+            RunTheApp(singleFile);
+        }
+
+        [Fact]
+        private void Bundled_Self_Contained_App_Run_Succeeds()
+        {
+            var fixture = sharedTestState.TestSelfContainedFixture.Copy();
+            var singleFile = BundleHelper.BundleApp(fixture);
+
+            // Run the bundled app (extract files)
+            RunTheApp(singleFile);
+
+            // Run the bundled app again (reuse extracted files)
+            RunTheApp(singleFile);
+        }
+
+        public class SharedTestState : IDisposable
+        {
+            public TestProjectFixture TestFrameworkDependentFixture { get; set; }
+            public TestProjectFixture TestSelfContainedFixture { get; set; }
+            public RepoDirectoriesProvider RepoDirectories { get; set; }
+
+            public SharedTestState()
+            {
+                RepoDirectories = new RepoDirectoriesProvider();
+
+                TestFrameworkDependentFixture = new TestProjectFixture("AppWithSubDirs", RepoDirectories);
+                TestFrameworkDependentFixture
+                    .EnsureRestoredForRid(TestFrameworkDependentFixture.CurrentRid, RepoDirectories.CorehostPackages)
+                    .PublishProject(runtime: TestFrameworkDependentFixture.CurrentRid,
+                                    outputDirectory: BundleHelper.GetPublishPath(TestFrameworkDependentFixture));
+
+                TestSelfContainedFixture = new TestProjectFixture("AppWithSubDirs", RepoDirectories);
+                TestSelfContainedFixture
+                    .EnsureRestoredForRid(TestSelfContainedFixture.CurrentRid, RepoDirectories.CorehostPackages)
+                    .PublishProject(runtime: TestSelfContainedFixture.CurrentRid,
+                                    outputDirectory: BundleHelper.GetPublishPath(TestSelfContainedFixture));
+            }
+
+            public void Dispose()
+            {
+                TestFrameworkDependentFixture.Dispose();
+                TestSelfContainedFixture.Dispose();
+            }
+        }
+    }
+}
diff --git a/src/installer/test/BundleTests/Helpers/BundleHelper.cs b/src/installer/test/BundleTests/Helpers/BundleHelper.cs
new file mode 100644 (file)
index 0000000..8139d6e
--- /dev/null
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using Microsoft.DotNet.CoreSetup.Test;
+
+namespace BundleTests.Helpers
+{
+    public static class BundleHelper
+    {
+        public const string DotnetBundleExtractBaseEnvVariable = "DOTNET_BUNDLE_EXTRACT_BASE_DIR";
+        public static string GetHostName(TestProjectFixture fixture)
+        {
+            return Path.GetFileName(fixture.TestProject.AppExe);
+        }
+
+        public static string GetPublishPath(TestProjectFixture fixture)
+        {
+            return Path.Combine(fixture.TestProject.ProjectDirectory, "publish");
+        }
+
+        public static DirectoryInfo GetBundleDir(TestProjectFixture fixture)
+        {
+            return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "bundle"));
+        }
+
+        public static DirectoryInfo GetExtractDir(TestProjectFixture fixture)
+        {
+            return Directory.CreateDirectory(Path.Combine(fixture.TestProject.ProjectDirectory, "extract"));
+        }
+
+        // Bundle to a single-file
+        // This step should be removed in favor of publishing with /p:PublishSingleFile=true
+        // once core-setup tests use 3.0 SDK 
+        public static string BundleApp(TestProjectFixture fixture)
+        {
+            var hostName = GetHostName(fixture);
+            string publishPath = GetPublishPath(fixture);
+            var bundleDir = GetBundleDir(fixture);
+
+            var bundler = new Microsoft.NET.HostModel.Bundle.Bundler(hostName, bundleDir.FullName);
+            string singleFile = bundler.GenerateBundle(publishPath);
+            return singleFile;
+        }
+
+    }
+}
diff --git a/src/installer/test/BundleTests/Helpers/BundleHelper.csproj b/src/installer/test/BundleTests/Helpers/BundleHelper.csproj
new file mode 100644 (file)
index 0000000..c1b5ea0
--- /dev/null
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>Bundle Test Helpers</Description>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <AssemblyName>BundleHelper</AssemblyName>
+    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\TestUtils\TestUtils.csproj" />
+    <ProjectReference Include="..\..\..\managed\Microsoft.NET.HostModel\Microsoft.NET.HostModel.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/BundleExtractRun.cs b/src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/BundleExtractRun.cs
new file mode 100644 (file)
index 0000000..3487e35
--- /dev/null
@@ -0,0 +1,116 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.IO;
+using Xunit;
+using Microsoft.DotNet.Cli.Build.Framework;
+using Microsoft.DotNet.CoreSetup.Test;
+using Microsoft.NET.HostModel.Bundle;
+using BundleTests.Helpers;
+
+namespace Microsoft.NET.HostModel.Tests
+{
+    public class BundleExtractRun : IClassFixture<BundleExtractRun.SharedTestState>
+    {
+        private SharedTestState sharedTestState;
+
+        public BundleExtractRun(BundleExtractRun.SharedTestState fixture)
+        {
+            sharedTestState = fixture;
+        }
+
+        public void RunTheApp(string path)
+        {
+            Command.Create(path)
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .Execute()
+                .Should()
+                .Pass()
+                .And
+                .HaveStdOutContaining("Wow! We now say hello to the big world and you.");
+        }
+
+        private void BundleExtractAndRun(TestProjectFixture fixture, string publishDir, string singleFileDir)
+        {
+            var hostName = BundleHelper.GetHostName(fixture);
+
+            // Run the App normally
+            RunTheApp(Path.Combine(publishDir, hostName));
+
+            // Bundle to a single-file
+            Bundler bundler = new Bundler(hostName, singleFileDir);
+            string singleFile = bundler.GenerateBundle(publishDir);
+
+            // Extract the file
+            Extractor extractor = new Extractor(singleFile, singleFileDir);
+            extractor.ExtractFiles();
+
+            // Run the extracted app
+            RunTheApp(singleFile);
+        }
+
+        private string RelativePath(string path)
+        {
+            return Path.GetRelativePath(Directory.GetCurrentDirectory(), path)
+                       .TrimEnd(Path.DirectorySeparatorChar);
+        }
+
+        [Fact]
+        public void TestWithAbsolutePaths()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            string publishDir = BundleHelper.GetPublishPath(fixture);
+            string outputDir = BundleHelper.GetBundleDir(fixture).FullName;
+
+            BundleExtractAndRun(fixture, publishDir, outputDir);
+        }
+
+        [Fact]
+        public void TestWithRelativePaths()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            string publishDir = RelativePath(BundleHelper.GetPublishPath(fixture));
+            string outputDir = RelativePath(BundleHelper.GetBundleDir(fixture).FullName);
+
+            BundleExtractAndRun(fixture, publishDir, outputDir);
+        }
+
+        [Fact]
+        public void TestWithRelativePathsDirSeparator()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            string publishDir = RelativePath(BundleHelper.GetPublishPath(fixture)) + Path.DirectorySeparatorChar;
+            string outputDir = RelativePath(BundleHelper.GetBundleDir(fixture).FullName) + Path.DirectorySeparatorChar;
+
+            BundleExtractAndRun(fixture, publishDir, outputDir);
+        }
+
+        public class SharedTestState : IDisposable
+        {
+            public TestProjectFixture TestFixture { get; set; }
+            public RepoDirectoriesProvider RepoDirectories { get; set; }
+
+            public SharedTestState()
+            {
+                RepoDirectories = new RepoDirectoriesProvider();
+
+                TestFixture = new TestProjectFixture("AppWithSubDirs", RepoDirectories);
+                TestFixture
+                    .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
+                    .PublishProject(runtime: TestFixture.CurrentRid,
+                                    outputDirectory: BundleHelper.GetPublishPath(TestFixture));
+            }
+
+            public void Dispose()
+            {
+                TestFixture.Dispose();
+            }
+        }
+    }
+}
diff --git a/src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs b/src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs
new file mode 100644 (file)
index 0000000..c823f5a
--- /dev/null
@@ -0,0 +1,149 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Xunit;
+using Microsoft.DotNet.CoreSetup.Test;
+using Microsoft.NET.HostModel.Bundle;
+using BundleTests.Helpers;
+
+namespace Microsoft.NET.HostModel.Tests
+{
+    public class BundlerConsistencyTests : IClassFixture<BundlerConsistencyTests.SharedTestState>
+    {
+        private SharedTestState sharedTestState;
+
+        public BundlerConsistencyTests(SharedTestState fixture)
+        {
+            sharedTestState = fixture;
+        }
+
+        [Fact]
+        public void TestWithEmptySpecFails()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            var hostName = BundleHelper.GetHostName(fixture);
+            var bundleDir = BundleHelper.GetBundleDir(fixture);
+            Bundler bundler = new Bundler(hostName, bundleDir.FullName);
+
+            FileSpec[][] invalidSpecs =
+            {
+                new FileSpec[] {new FileSpec(hostName, null) },
+                new FileSpec[] {new FileSpec(hostName, "") },
+                new FileSpec[] {new FileSpec(hostName, "    ") }
+            };
+
+            foreach (var invalidSpec in invalidSpecs)
+            {
+                Assert.Throws<ArgumentException>(() => bundler.GenerateBundle(invalidSpec));
+            }
+        }
+
+        [Fact]
+        public void TestWithoutSpecifyingHostFails()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            var hostName = BundleHelper.GetHostName(fixture);
+            var appName = Path.GetFileNameWithoutExtension(hostName);
+            string publishPath = BundleHelper.GetPublishPath(fixture);
+            var bundleDir = BundleHelper.GetBundleDir(fixture);
+
+            // Generate a file specification without the apphost
+            var fileSpecs = new List<FileSpec>();
+            string[] files = { $"{appName}.dll", $"{appName}.deps.json", $"{appName}.runtimeconfig.json" };
+            Array.ForEach(files, x => fileSpecs.Add(new FileSpec(x, x)));
+
+            Bundler bundler = new Bundler(hostName, bundleDir.FullName);
+
+            Assert.Throws<ArgumentException>(() => bundler.GenerateBundle(fileSpecs));
+        }
+
+        [InlineData(true)]
+        [InlineData(false)]
+        [Theory]
+        public void TestFilesNotBundled(bool embedPDBs)
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            var hostName = BundleHelper.GetHostName(fixture);
+            var appName = Path.GetFileNameWithoutExtension(hostName);
+            string publishPath = BundleHelper.GetPublishPath(fixture);
+            var bundleDir = BundleHelper.GetBundleDir(fixture);
+            
+            // Make up a app.runtimeconfig.dev.json file in the publish directory.
+            File.Copy(Path.Combine(publishPath, $"{appName}.runtimeconfig.json"), 
+                      Path.Combine(publishPath, $"{appName}.runtimeconfig.dev.json"));
+
+            var singleFile = new Bundler(hostName, bundleDir.FullName, embedPDBs).GenerateBundle(publishPath);
+
+            bundleDir.Should().OnlyHaveFiles(new string[] { hostName });
+
+            new Extractor(singleFile, bundleDir.FullName).ExtractFiles();
+
+            bundleDir.Should().NotHaveFile($"{appName}.runtimeconfig.dev.json");
+            if (!embedPDBs)
+            {
+                bundleDir.Should().NotHaveFile($"{appName}.pdb");
+            }
+        }
+
+        [Fact]
+        public void ExtractingANonBundleFails()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            var hostName = BundleHelper.GetHostName(fixture);
+            var hostExe = Path.Combine(BundleHelper.GetPublishPath(fixture), hostName);
+
+            var bundleDir = BundleHelper.GetBundleDir(fixture);
+            Extractor extractor = new Extractor(hostExe, "extract");
+            Assert.Throws<BundleException>(() => extractor.ExtractFiles());
+        }
+
+        [Fact]
+        public void AllBundledFilesAreExtracted()
+        {
+            var fixture = sharedTestState.TestFixture.Copy();
+
+            var hostName = BundleHelper.GetHostName(fixture);
+            var bundleDir = BundleHelper.GetBundleDir(fixture);
+
+            var bundler = new Bundler(hostName, bundleDir.FullName);
+            string singleFile = bundler.GenerateBundle(BundleHelper.GetPublishPath(fixture));
+
+            var expectedFiles = new List<string>(bundler.BundleManifest.Files.Count);
+            expectedFiles.Add(hostName);
+            bundler.BundleManifest.Files.ForEach(file => expectedFiles.Add(file.RelativePath));
+
+            new Extractor(singleFile, bundleDir.FullName).ExtractFiles();
+            bundleDir.Should().OnlyHaveFiles(expectedFiles);
+        }
+
+        public class SharedTestState : IDisposable
+        {
+            public TestProjectFixture TestFixture { get; set; }
+            public RepoDirectoriesProvider RepoDirectories { get; set; }
+
+            public SharedTestState()
+            {
+                RepoDirectories = new RepoDirectoriesProvider();
+
+                TestFixture = new TestProjectFixture("StandaloneApp", RepoDirectories);
+                TestFixture
+                    .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
+                    .PublishProject(runtime: TestFixture.CurrentRid,
+                                    outputDirectory: BundleHelper.GetPublishPath(TestFixture));
+            }
+
+            public void Dispose()
+            {
+                TestFixture.Dispose();
+            }
+        }
+    }
+}
diff --git a/src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/Microsoft.NET.HostModel.Bundle.Tests.csproj b/src/installer/test/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/Microsoft.NET.HostModel.Bundle.Tests.csproj
new file mode 100644 (file)
index 0000000..277091b
--- /dev/null
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>Microsoft.NET.HostModel.Bundle Tests</Description>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <AssemblyName>Microsoft.NET.HostModel.Bundle.Tests</AssemblyName>
+    <PackageId>Microsoft.NET.HostModel.Bundle.Tests</PackageId>
+    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\TestUtils\TestUtils.csproj" />
+    <ProjectReference Include="..\..\..\managed\Microsoft.NET.HostModel\Microsoft.NET.HostModel.csproj" />
+    <ProjectReference Include="..\Helpers\BundleHelper.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
+    <PackageReference Include="xunit" Version="2.2.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/installer/test/HostActivationTests/BundledAppWithSubDirs.cs b/src/installer/test/HostActivationTests/BundledAppWithSubDirs.cs
deleted file mode 100644 (file)
index d877497..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.IO;
-using Xunit;
-using Microsoft.DotNet.Cli.Build.Framework;
-
-namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
-{
-    public class BundledAppWithSubDirs : IClassFixture<BundledAppWithSubDirs.SharedTestState>
-    {
-        private SharedTestState sharedTestState;
-
-        public BundledAppWithSubDirs(SharedTestState fixture)
-        {
-            sharedTestState = fixture;
-        }
-
-        [Fact]
-        private void Bundle_And_Run_App_With_Subdirs_Succeeds()
-        {
-            var fixture = sharedTestState.TestFixture.Copy();
-            var hostName = Path.GetFileName(fixture.TestProject.AppExe);
-
-            // Bundle to a single-file
-            // This step should be removed in favor of publishing with /p:PublishSingleFile=true
-            // once associated changes in SDK repo are checked in.
-            string singleFileDir = Path.Combine(fixture.TestProject.ProjectDirectory, "oneExe");
-            Directory.CreateDirectory(singleFileDir);
-            var bundler = new Microsoft.NET.HostModel.Bundle.Bundler(hostName, singleFileDir);
-            string singleFile = bundler.GenerateBundle(fixture.TestProject.OutputDirectory);
-
-            // Run the bundled app (extract files)
-            Command.Create(singleFile)
-                .CaptureStdErr()
-                .CaptureStdOut()
-                .Execute()
-                .Should()
-                .Pass()
-                .And
-                .HaveStdOutContaining("Wow! We now say hello to the big world and you.");
-
-            // Run the bundled app again (reuse extracted files)
-            Command.Create(singleFile)
-                .CaptureStdErr()
-                .CaptureStdOut()
-                .Execute()
-                .Should()
-                .Pass()
-                .And
-                .HaveStdOutContaining("Wow! We now say hello to the big world and you.");
-        }
-
-        public class SharedTestState : IDisposable
-        {
-            public TestProjectFixture TestFixture { get; set; }
-            public RepoDirectoriesProvider RepoDirectories { get; set; }
-
-            public SharedTestState()
-            {
-                RepoDirectories = new RepoDirectoriesProvider();
-
-                TestFixture = new TestProjectFixture("StandaloneAppWithSubDirs", RepoDirectories);
-                TestFixture
-                    .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
-                    .PublishProject(runtime: TestFixture.CurrentRid);
-            }
-
-            public void Dispose()
-            {
-                TestFixture.Dispose();
-            }
-        }
-    }
-}
diff --git a/src/installer/test/Microsoft.NET.HostModel.Tests/BundleAndExtract.cs b/src/installer/test/Microsoft.NET.HostModel.Tests/BundleAndExtract.cs
deleted file mode 100644 (file)
index 51246b0..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.IO;
-using Xunit;
-using Microsoft.DotNet.Cli.Build.Framework;
-using Microsoft.NET.HostModel.Bundle;
-
-namespace Microsoft.DotNet.CoreSetup.Test.BundleTests.BundleExtract
-{
-    public class BundleAndExtract : IClassFixture<BundleAndExtract.SharedTestState>
-    {
-        private SharedTestState sharedTestState;
-
-        public BundleAndExtract(BundleAndExtract.SharedTestState fixture)
-        {
-            sharedTestState = fixture;
-        }
-
-        private void Run(TestProjectFixture fixture, string publishDir, string singleFileDir)
-        {
-            var dotnet = fixture.SdkDotnet;
-            var hostName = Path.GetFileName(fixture.TestProject.AppExe);
-
-            // Run the App normally
-            Command.Create(Path.Combine(publishDir, hostName))
-                .CaptureStdErr()
-                .CaptureStdOut()
-                .Execute()
-                .Should()
-                .Pass()
-                .And
-                .HaveStdOutContaining("Wow! We now say hello to the big world and you.");
-
-            // Bundle to a single-file
-            Bundler bundler = new Bundler(hostName, singleFileDir);
-            string singleFile = bundler.GenerateBundle(publishDir);
-
-            // Extract the file
-            Extractor extractor = new Extractor(singleFile, singleFileDir);
-            extractor.ExtractFiles();
-
-            // Run the extracted app
-            Command.Create(singleFile)
-                .CaptureStdErr()
-                .CaptureStdOut()
-                .Execute()
-                .Should()
-                .Pass()
-                .And
-                .HaveStdOutContaining("Wow! We now say hello to the big world and you.");
-        }
-
-        private string GetSingleFileDir(TestProjectFixture fixture)
-        {
-            // Create a directory for bundle/extraction output.
-            // This directory shouldn't be within TestProject.OutputDirectory, since the bundler
-            // will (attempt to) embed all files below the TestProject.OutputDirectory tree into one file.
-
-            string singleFileDir = Path.Combine(fixture.TestProject.ProjectDirectory, "oneExe");
-            Directory.CreateDirectory(singleFileDir);
-
-            return singleFileDir;
-        }
-
-        private string RelativePath(string path)
-        {
-            return Path.GetRelativePath(Directory.GetCurrentDirectory(), path)
-                       .TrimEnd(Path.DirectorySeparatorChar);
-        }
-
-        [Fact]
-        public void TestWithAbsolutePaths()
-        {
-            var fixture = sharedTestState.TestFixture
-                .Copy();
-
-            string publishDir = fixture.TestProject.OutputDirectory;
-            string singleFileDir = GetSingleFileDir(fixture);
-
-            Run(fixture, publishDir, singleFileDir);
-        }
-
-        [Fact]
-        public void TestWithRelativePaths()
-        {
-            var fixture = sharedTestState.TestFixture
-                .Copy();
-
-            string publishDir = RelativePath(fixture.TestProject.OutputDirectory);
-            string singleFileDir = RelativePath(GetSingleFileDir(fixture));
-
-            Run(fixture, publishDir, singleFileDir);
-        }
-
-        [Fact]
-        public void TestWithRelativePathsDirSeparator()
-        {
-            var fixture = sharedTestState.TestFixture
-                .Copy();
-
-            string publishDir = RelativePath(fixture.TestProject.OutputDirectory) + Path.DirectorySeparatorChar;
-            string singleFileDir = RelativePath(GetSingleFileDir(fixture)) + Path.DirectorySeparatorChar;
-
-            Run(fixture, publishDir, singleFileDir);
-        }
-
-        public class SharedTestState : IDisposable
-        {
-            public TestProjectFixture TestFixture { get; set; }
-            public RepoDirectoriesProvider RepoDirectories { get; set; }
-
-            public SharedTestState()
-            {
-                RepoDirectories = new RepoDirectoriesProvider();
-
-                TestFixture = new TestProjectFixture("StandaloneAppWithSubDirs", RepoDirectories);
-                TestFixture
-                    .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages)
-                    .PublishProject(runtime: TestFixture.CurrentRid);
-            }
-
-            public void Dispose()
-            {
-                TestFixture.Dispose();
-            }
-        }
-    }
-}
index 54c8c36..b70636f 100644 (file)
@@ -74,6 +74,15 @@ namespace Microsoft.DotNet.CoreSetup.Test
             return new AndConstraint<DirectoryInfoAssertions>(new DirectoryInfoAssertions(dir));
         }
 
+        public AndConstraint<DirectoryInfoAssertions> NotHaveDirectory(string expectedDir)
+        {
+            var dir = _dirInfo.EnumerateDirectories(expectedDir, SearchOption.TopDirectoryOnly).SingleOrDefault();
+            Execute.Assertion.ForCondition(dir == null)
+                .FailWith("Directory {0} should not be found in found inside directory {1}.", expectedDir, _dirInfo.FullName);
+
+            return new AndConstraint<DirectoryInfoAssertions>(new DirectoryInfoAssertions(dir));
+        }
+
         public AndConstraint<DirectoryInfoAssertions> OnlyHaveFiles(IEnumerable<string> expectedFiles)
         {
             var actualFiles = _dirInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly).Select(f => f.Name);
@@ -89,5 +98,17 @@ namespace Microsoft.DotNet.CoreSetup.Test
 
             return new AndConstraint<DirectoryInfoAssertions>(this);
         }
+
+        public AndConstraint<DirectoryInfoAssertions> NotBeModifiedAfter(DateTime timeUtc)
+        {
+            _dirInfo.Refresh();
+            DateTime writeTime = _dirInfo.LastWriteTimeUtc;
+
+            Execute.Assertion.ForCondition(writeTime <= timeUtc)
+                .FailWith("Directory {0} should not be modified after {1}, but is modified at {2}.", _dirInfo.FullName, timeUtc, writeTime);
+
+            return new AndConstraint<DirectoryInfoAssertions>(this);
+        }
+
     }
 }
index f92e0de..79f420b 100644 (file)
@@ -242,6 +242,7 @@ namespace Microsoft.DotNet.CoreSetup.Test
             DotNetCli dotnet = null,
             string runtime = null,
             string framework = null,
+            string selfContained = null,
             string outputDirectory = null)
         {
             dotnet = dotnet ?? SdkDotnet;
@@ -268,6 +269,12 @@ namespace Microsoft.DotNet.CoreSetup.Test
                 publishArgs.Add($"/p:NETCoreAppFramework={framework}");
             }
 
+            if (selfContained != null)
+            {
+                publishArgs.Add("--self-contained");
+                publishArgs.Add(selfContained);
+            }
+
             if (outputDirectory != null)
             {
                 publishArgs.Add("-o");
index 9c13c25..b5d3fb0 100644 (file)
@@ -10,7 +10,8 @@
   <ItemGroup>
     <TestProjects Include="$(TestDir)/HostActivationTests/HostActivationTests.csproj" />
     <TestProjects Include="$(TestDir)/Microsoft.Extensions.DependencyModel.Tests/Microsoft.Extensions.DependencyModel.Tests.csproj" />
-    <TestProjects Include="$(TestDir)/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Tests.csproj" />
+    <TestProjects Include="$(TestDir)/BundleTests/Microsoft.NET.HostModel.Bundle.Tests/Microsoft.NET.HostModel.Bundle.Tests.csproj" />
+    <TestProjects Include="$(TestDir)/BundleTests/AppHost.Bundle.Tests/AppHost.Bundle.Tests.csproj" />
   </ItemGroup>
   <ItemGroup>
     <RestoreTestFallbackSource Include="$(CoreHostOutputDir.TrimEnd('/').TrimEnd('\'))" />