diff --git a/src/coreclr/src/binder/applicationcontext.cpp b/src/coreclr/src/binder/applicationcontext.cpp index d190295485410..f865b4240490d 100644 --- a/src/coreclr/src/binder/applicationcontext.cpp +++ b/src/coreclr/src/binder/applicationcontext.cpp @@ -127,74 +127,6 @@ namespace BINDER_SPACE return hr; } - HRESULT GetNextPath(SString& paths, SString::Iterator& startPos, SString& outPath) - { - HRESULT hr = S_OK; - - bool wrappedWithQuotes = false; - - // Skip any leading spaces or path separators - while (paths.Skip(startPos, W(' ')) || paths.Skip(startPos, PATH_SEPARATOR_CHAR_W)) {} - - if (startPos == paths.End()) - { - // No more paths in the string and we just skipped over some white space - outPath.Set(W("")); - return S_FALSE; - } - - // Support paths being wrapped with quotations - if (paths.Skip(startPos, W('\"'))) - { - wrappedWithQuotes = true; - } - - SString::Iterator iEnd = startPos; // Where current path ends - SString::Iterator iNext; // Where next path starts - if (wrappedWithQuotes) - { - if (paths.Find(iEnd, W('\"'))) - { - iNext = iEnd; - // Find where the next path starts - there should be a path separator right after the closing quotation mark - if (paths.Find(iNext, PATH_SEPARATOR_CHAR_W)) - { - iNext++; - } - else - { - iNext = paths.End(); - } - } - else - { - // There was no terminating quotation mark - that's bad - GO_WITH_HRESULT(E_INVALIDARG); - } - } - else if (paths.Find(iEnd, PATH_SEPARATOR_CHAR_W)) - { - iNext = iEnd + 1; - } - else - { - iNext = iEnd = paths.End(); - } - - // Skip any trailing spaces - while (iEnd[-1] == W(' ')) - { - iEnd--; - } - - _ASSERTE(startPos < iEnd); - - outPath.Set(paths, startPos, iEnd); - startPos = iNext; - Exit: - return hr; - } - HRESULT ApplicationContext::SetupBindingPaths(SString &sTrustedPlatformAssemblies, SString &sPlatformResourceRoots, SString &sAppPaths, @@ -221,64 +153,15 @@ namespace BINDER_SPACE for (SString::Iterator i = sTrustedPlatformAssemblies.Begin(); i != sTrustedPlatformAssemblies.End(); ) { SString fileName; + SString simpleName; + bool isNativeImage = false; HRESULT pathResult = S_OK; - IF_FAIL_GO(pathResult = GetNextPath(sTrustedPlatformAssemblies, i, fileName)); + IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ false, fileName, simpleName, isNativeImage)); if (pathResult == S_FALSE) { break; } -#ifndef CROSSGEN_COMPILE - if (Path::IsRelative(fileName)) - { - GO_WITH_HRESULT(E_INVALIDARG); - } -#endif - - // Find the beginning of the simple name - SString::Iterator iSimpleNameStart = fileName.End(); - - if (!fileName.FindBack(iSimpleNameStart, DIRECTORY_SEPARATOR_CHAR_W)) - { - iSimpleNameStart = fileName.Begin(); - } - else - { - // Advance past the directory separator to the first character of the file name - iSimpleNameStart++; - } - - if (iSimpleNameStart == fileName.End()) - { - GO_WITH_HRESULT(E_INVALIDARG); - } - - SString simpleName; - bool isNativeImage = false; - - // GCC complains if we create SStrings inline as part of a function call - SString sNiDll(W(".ni.dll")); - SString sNiExe(W(".ni.exe")); - SString sDll(W(".dll")); - SString sExe(W(".exe")); - - if (fileName.EndsWithCaseInsensitive(sNiDll) || - fileName.EndsWithCaseInsensitive(sNiExe)) - { - simpleName.Set(fileName, iSimpleNameStart, fileName.End() - 7); - isNativeImage = true; - } - else if (fileName.EndsWithCaseInsensitive(sDll) || - fileName.EndsWithCaseInsensitive(sExe)) - { - simpleName.Set(fileName, iSimpleNameStart, fileName.End() - 4); - } - else - { - // Invalid filename - GO_WITH_HRESULT(E_INVALIDARG); - } - const SimpleNameToFileNameMapEntry *pExistingEntry = m_pTrustedPlatformAssemblyMap->LookupPtr(simpleName.GetUnicode()); if (pExistingEntry != nullptr) diff --git a/src/coreclr/src/binder/assemblybinder.cpp b/src/coreclr/src/binder/assemblybinder.cpp index 02a3646dab4a1..aa29465af824e 100644 --- a/src/coreclr/src/binder/assemblybinder.cpp +++ b/src/coreclr/src/binder/assemblybinder.cpp @@ -22,6 +22,7 @@ #include "utils.hpp" #include "variables.hpp" #include "stringarraylist.h" +#include "configuration.h" #define APP_DOMAIN_LOCKED_UNLOCKED 0x02 #define APP_DOMAIN_LOCKED_CONTEXT 0x04 @@ -401,14 +402,59 @@ namespace BINDER_SPACE sCoreLib.Set(systemDirectory); CombinePath(sCoreLib, sCoreLibName, sCoreLib); - IF_FAIL_GO(AssemblyBinder::GetAssembly(sCoreLib, - TRUE /* fIsInGAC */, - fBindToNativeImage, - &pSystemAssembly, - NULL /* szMDAssemblyPath */, - bundleFileLocation)); + hr = AssemblyBinder::GetAssembly(sCoreLib, + TRUE /* fIsInGAC */, + fBindToNativeImage, + &pSystemAssembly, + NULL /* szMDAssemblyPath */, + bundleFileLocation); BinderTracing::PathProbed(sCoreLib, pathSource, hr); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + // Try to find corelib in the TPA + StackSString sCoreLibSimpleName(CoreLibName_W); + StackSString sTrustedPlatformAssemblies = Configuration::GetKnobStringValue(W("TRUSTED_PLATFORM_ASSEMBLIES")); + sTrustedPlatformAssemblies.Normalize(); + + bool found = false; + for (SString::Iterator i = sTrustedPlatformAssemblies.Begin(); i != sTrustedPlatformAssemblies.End(); ) + { + SString fileName; + SString simpleName; + bool isNativeImage = false; + HRESULT pathResult = S_OK; + IF_FAIL_GO(pathResult = GetNextTPAPath(sTrustedPlatformAssemblies, i, /*dllOnly*/ true, fileName, simpleName, isNativeImage)); + if (pathResult == S_FALSE) + { + break; + } + + if (simpleName.EqualsCaseInsensitive(sCoreLibSimpleName)) + { + sCoreLib = fileName; + found = true; + break; + } + } + + if (!found) + { + GO_WITH_HRESULT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + } + + hr = AssemblyBinder::GetAssembly(sCoreLib, + TRUE /* fIsInGAC */, + fBindToNativeImage, + &pSystemAssembly, + NULL /* szMDAssemblyPath */, + bundleFileLocation); + + BinderTracing::PathProbed(sCoreLib, BinderTracing::PathSource::ApplicationAssemblies, hr); + } + + IF_FAIL_GO(hr); + *ppSystemAssembly = pSystemAssembly.Extract(); Exit: diff --git a/src/coreclr/src/binder/inc/utils.hpp b/src/coreclr/src/binder/inc/utils.hpp index 6108d3aea22c3..1c8ea114f5e6c 100644 --- a/src/coreclr/src/binder/inc/utils.hpp +++ b/src/coreclr/src/binder/inc/utils.hpp @@ -39,6 +39,9 @@ namespace BINDER_SPACE SBuffer &publicKeyTokenBLOB); BOOL IsFileNotFound(HRESULT hr); + + HRESULT GetNextPath(SString& paths, SString::Iterator& startPos, SString& outPath); + HRESULT GetNextTPAPath(SString& paths, SString::Iterator& startPos, bool dllOnly, SString& outPath, SString& simpleName, bool& isNativeImage); }; #endif diff --git a/src/coreclr/src/binder/utils.cpp b/src/coreclr/src/binder/utils.cpp index c98d7f71bf831..21fc115a77662 100644 --- a/src/coreclr/src/binder/utils.cpp +++ b/src/coreclr/src/binder/utils.cpp @@ -15,6 +15,8 @@ #include "strongnameinternal.h" #include "corpriv.h" +#include "clr/fs/path.h" +using namespace clr::fs; namespace BINDER_SPACE { @@ -115,4 +117,139 @@ namespace BINDER_SPACE { return RuntimeFileNotFound(hr); } + + HRESULT GetNextPath(SString& paths, SString::Iterator& startPos, SString& outPath) + { + HRESULT hr = S_OK; + + bool wrappedWithQuotes = false; + + // Skip any leading spaces or path separators + while (paths.Skip(startPos, W(' ')) || paths.Skip(startPos, PATH_SEPARATOR_CHAR_W)) {} + + if (startPos == paths.End()) + { + // No more paths in the string and we just skipped over some white space + outPath.Set(W("")); + return S_FALSE; + } + + // Support paths being wrapped with quotations + if (paths.Skip(startPos, W('\"'))) + { + wrappedWithQuotes = true; + } + + SString::Iterator iEnd = startPos; // Where current path ends + SString::Iterator iNext; // Where next path starts + if (wrappedWithQuotes) + { + if (paths.Find(iEnd, W('\"'))) + { + iNext = iEnd; + // Find where the next path starts - there should be a path separator right after the closing quotation mark + if (paths.Find(iNext, PATH_SEPARATOR_CHAR_W)) + { + iNext++; + } + else + { + iNext = paths.End(); + } + } + else + { + // There was no terminating quotation mark - that's bad + GO_WITH_HRESULT(E_INVALIDARG); + } + } + else if (paths.Find(iEnd, PATH_SEPARATOR_CHAR_W)) + { + iNext = iEnd + 1; + } + else + { + iNext = iEnd = paths.End(); + } + + // Skip any trailing spaces + while (iEnd[-1] == W(' ')) + { + iEnd--; + } + + _ASSERTE(startPos < iEnd); + + outPath.Set(paths, startPos, iEnd); + startPos = iNext; + Exit: + return hr; + } + + HRESULT GetNextTPAPath(SString& paths, SString::Iterator& startPos, bool dllOnly, SString& outPath, SString& simpleName, bool& isNativeImage) + { + HRESULT hr = S_OK; + isNativeImage = false; + + HRESULT pathResult = S_OK; + IF_FAIL_GO(pathResult = GetNextPath(paths, startPos, outPath)); + if (pathResult == S_FALSE) + { + return S_FALSE; + } + +#ifndef CROSSGEN_COMPILE + if (Path::IsRelative(outPath)) + { + GO_WITH_HRESULT(E_INVALIDARG); + } +#endif + + { + // Find the beginning of the simple name + SString::Iterator iSimpleNameStart = outPath.End(); + + if (!outPath.FindBack(iSimpleNameStart, DIRECTORY_SEPARATOR_CHAR_W)) + { + iSimpleNameStart = outPath.Begin(); + } + else + { + // Advance past the directory separator to the first character of the file name + iSimpleNameStart++; + } + + if (iSimpleNameStart == outPath.End()) + { + GO_WITH_HRESULT(E_INVALIDARG); + } + + // GCC complains if we create SStrings inline as part of a function call + SString sNiDll(W(".ni.dll")); + SString sNiExe(W(".ni.exe")); + SString sDll(W(".dll")); + SString sExe(W(".exe")); + + if (!dllOnly && (outPath.EndsWithCaseInsensitive(sNiDll) || + outPath.EndsWithCaseInsensitive(sNiExe))) + { + simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 7); + isNativeImage = true; + } + else if (outPath.EndsWithCaseInsensitive(sDll) || + (!dllOnly && outPath.EndsWithCaseInsensitive(sExe))) + { + simpleName.Set(outPath, iSimpleNameStart, outPath.End() - 4); + } + else + { + // Invalid filename + GO_WITH_HRESULT(E_INVALIDARG); + } + } + + Exit: + return hr; + } + }; diff --git a/src/installer/corehost/cli/bundle/file_entry.cpp b/src/installer/corehost/cli/bundle/file_entry.cpp index 0249b249a0359..e33b2cfa32dcf 100644 --- a/src/installer/corehost/cli/bundle/file_entry.cpp +++ b/src/installer/corehost/cli/bundle/file_entry.cpp @@ -14,11 +14,11 @@ bool file_entry_t::is_valid() const static_cast(m_type) < file_type_t::__last; } -file_entry_t file_entry_t::read(reader_t &reader) +file_entry_t file_entry_t::read(reader_t &reader, bool force_extraction) { // First read the fixed-sized portion of file-entry const file_entry_fixed_t* fixed_data = reinterpret_cast(reader.read_direct(sizeof(file_entry_fixed_t))); - file_entry_t entry(fixed_data); + file_entry_t entry(fixed_data, force_extraction); if (!entry.is_valid()) { @@ -35,6 +35,9 @@ file_entry_t file_entry_t::read(reader_t &reader) bool file_entry_t::needs_extraction() const { + if (m_force_extraction) + return true; + switch (m_type) { case file_type_t::deps_json: diff --git a/src/installer/corehost/cli/bundle/file_entry.h b/src/installer/corehost/cli/bundle/file_entry.h index 9d45eaecb317f..eb122efe86835 100644 --- a/src/installer/corehost/cli/bundle/file_entry.h +++ b/src/installer/corehost/cli/bundle/file_entry.h @@ -38,12 +38,16 @@ namespace bundle , m_type(file_type_t::__last) , m_relative_path() , m_disabled(false) + , m_force_extraction(false) { } - file_entry_t(const file_entry_fixed_t *fixed_data) - :m_relative_path() + file_entry_t( + const file_entry_fixed_t *fixed_data, + const bool force_extraction = false) + : m_relative_path() , m_disabled(false) + , m_force_extraction(force_extraction) { // File_entries in the bundle-manifest are expected to be used // beyond startup (for loading files directly from bundle, lazy extraction, etc.). @@ -64,7 +68,7 @@ namespace bundle bool needs_extraction() const; bool matches(const pal::string_t& path) const { return (pal::pathcmp(relative_path(), path) == 0) && !is_disabled(); } - static file_entry_t read(reader_t &reader); + static file_entry_t read(reader_t &reader, bool force_extraction); private: int64_t m_offset; @@ -76,6 +80,7 @@ namespace bundle // So in order to make sure that the servicing location is used, the file entry in the bundle is marked as "disabled" // in such case, and the lookup logic will behave as if the file is not present in the bundle. bool m_disabled; + bool m_force_extraction; bool is_valid() const; }; } diff --git a/src/installer/corehost/cli/bundle/manifest.cpp b/src/installer/corehost/cli/bundle/manifest.cpp index 23678a563d59a..cf439952871c7 100644 --- a/src/installer/corehost/cli/bundle/manifest.cpp +++ b/src/installer/corehost/cli/bundle/manifest.cpp @@ -5,15 +5,15 @@ using namespace bundle; -manifest_t manifest_t::read(reader_t& reader, int32_t num_files) +manifest_t manifest_t::read(reader_t& reader, const header_t& header) { manifest_t manifest; - for (int32_t i = 0; i < num_files; i++) + for (int32_t i = 0; i < header.num_embedded_files(); i++) { - file_entry_t entry = file_entry_t::read(reader); + file_entry_t entry = file_entry_t::read(reader, header.is_netcoreapp3_compat_mode()); manifest.files.push_back(std::move(entry)); - manifest.m_need_extraction |= entry.needs_extraction(); + manifest.m_files_need_extraction |= entry.needs_extraction(); } return manifest; diff --git a/src/installer/corehost/cli/bundle/manifest.h b/src/installer/corehost/cli/bundle/manifest.h index 159f810189cf2..c744e6290ee30 100644 --- a/src/installer/corehost/cli/bundle/manifest.h +++ b/src/installer/corehost/cli/bundle/manifest.h @@ -6,6 +6,7 @@ #include #include "file_entry.h" +#include "header.h" namespace bundle { @@ -16,16 +17,21 @@ namespace bundle { public: manifest_t() - : m_need_extraction(false) {} + : m_files_need_extraction(false) + { + } std::vector files; - static manifest_t read(reader_t &reader, int32_t num_files); + static manifest_t read(reader_t &reader, const header_t &header); - bool files_need_extraction() { return m_need_extraction; } + bool files_need_extraction() const + { + return m_files_need_extraction; + } private: - bool m_need_extraction; + bool m_files_need_extraction; }; } #endif // __MANIFEST_H__ diff --git a/src/installer/corehost/cli/bundle/runner.cpp b/src/installer/corehost/cli/bundle/runner.cpp index 012274cc9ed0e..36edd791fa44d 100644 --- a/src/installer/corehost/cli/bundle/runner.cpp +++ b/src/installer/corehost/cli/bundle/runner.cpp @@ -28,7 +28,7 @@ StatusCode runner_t::extract() m_runtimeconfig_json.set_location(&m_header.runtimeconfig_json_location()); // Read the bundle manifest - m_manifest = manifest_t::read(reader, m_header.num_embedded_files()); + m_manifest = manifest_t::read(reader, m_header); // Extract the files if necessary if (m_manifest.files_need_extraction()) @@ -65,7 +65,8 @@ bool runner_t::probe(const pal::string_t& relative_path, int64_t* offset, int64_ { const bundle::file_entry_t* entry = probe(relative_path); - if (entry == nullptr) + // Do not report extracted entries - those should be reported through either TPA or resource paths + if (entry == nullptr || entry->needs_extraction()) { return false; } diff --git a/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp b/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp index ceffb77f3b8a0..d406497e7be1b 100644 --- a/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp +++ b/src/installer/corehost/cli/hostpolicy/deps_resolver.cpp @@ -736,7 +736,8 @@ void deps_resolver_t::get_app_context_deps_files_range(fx_definition_vector_t::i auto begin_iter = m_fx_definitions.begin(); auto end_iter = m_fx_definitions.end(); - if ((m_host_mode == host_mode_t::libhost || bundle::info_t::is_single_file_bundle()) + if ((m_host_mode == host_mode_t::libhost + || (bundle::info_t::is_single_file_bundle() && !bundle::runner_t::app()->is_netcoreapp3_compat_mode())) && begin_iter != end_iter) { // Neither in a libhost scenario nor in a bundled app diff --git a/src/installer/corehost/cli/hostpolicy/deps_resolver.h b/src/installer/corehost/cli/hostpolicy/deps_resolver.h index 300973ce2a4cc..92749558e78ce 100644 --- a/src/installer/corehost/cli/hostpolicy/deps_resolver.h +++ b/src/installer/corehost/cli/hostpolicy/deps_resolver.h @@ -172,13 +172,15 @@ class deps_resolver_t return m_is_framework_dependent; } - const pal::string_t &get_app_dir() const + void get_app_dir(pal::string_t *app_dir) const { if (m_host_mode == host_mode_t::libhost) { static const pal::string_t s_empty; - return s_empty; + *app_dir = s_empty; + return; } + *app_dir = m_app_dir; if (m_host_mode == host_mode_t::apphost) { if (bundle::info_t::is_single_file_bundle()) @@ -186,12 +188,18 @@ class deps_resolver_t const bundle::runner_t* app = bundle::runner_t::app(); if (app->is_netcoreapp3_compat_mode()) { - return app->extraction_path(); + *app_dir = app->extraction_path(); } } } - return m_app_dir; + // Make sure the path ends with a directory separator + // This has been the behavior for a long time, and we should make it consistent + // for all cases. + if (app_dir->back() != DIR_SEPARATOR) + { + app_dir->append(1, DIR_SEPARATOR); + } } private: diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp index dbe883eb8410f..9939fe4d71c76 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp @@ -369,11 +369,21 @@ int corehost_main_init( if (bundle::info_t::is_single_file_bundle()) { - StatusCode status = bundle::runner_t::process_manifest_and_extract(); + const bundle::runner_t* bundle = bundle::runner_t::app(); + StatusCode status = bundle->process_manifest_and_extract(); if (status != StatusCode::Success) { return status; } + + if (bundle->is_netcoreapp3_compat_mode()) + { + auto extracted_assembly = bundle->extraction_path(); + auto app_name = hostpolicy_init.host_info.get_app_name() + _X(".dll"); + append_path(&extracted_assembly, app_name.c_str()); + assert(pal::file_exists(extracted_assembly)); + hostpolicy_init.host_info.app_path = extracted_assembly; + } } return corehost_init(hostpolicy_init, argc, argv, location, args); diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy_context.cpp b/src/installer/corehost/cli/hostpolicy/hostpolicy_context.cpp index ee8ad4293abb3..502d3a1b4a66b 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy_context.cpp +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy_context.cpp @@ -161,12 +161,26 @@ int hostpolicy_context_t::initialize(hostpolicy_init_t &hostpolicy_init, const a if (fx_curr != fx_begin) app_context_deps_str += _X(';'); - app_context_deps_str += (*fx_curr)->get_deps_file(); + // For the application's .deps.json if this is single file, 3.1 backward compat + // then the path used internally is the bundle path, but externally we need to report + // the path to the extraction folder. + if (fx_curr == fx_begin && bundle::info_t::is_single_file_bundle() && bundle::runner_t::app()->is_netcoreapp3_compat_mode()) + { + pal::string_t deps_path = bundle::runner_t::app()->extraction_path(); + append_path(&deps_path, get_filename((*fx_curr)->get_deps_file()).c_str()); + app_context_deps_str += deps_path; + } + else + { + app_context_deps_str += (*fx_curr)->get_deps_file(); + } + ++fx_curr; } // Build properties for CoreCLR instantiation - pal::string_t app_base = resolver.get_app_dir(); + pal::string_t app_base; + resolver.get_app_dir(&app_base); coreclr_properties.add(common_property::TrustedPlatformAssemblies, probe_paths.tpa.c_str()); coreclr_properties.add(common_property::NativeDllSearchDirectories, probe_paths.native.c_str()); coreclr_properties.add(common_property::PlatformResourceRoots, probe_paths.resources.c_str()); diff --git a/src/installer/tests/Assets/TestProjects/AppWithSubDirs/AppWithSubDirs.csproj b/src/installer/tests/Assets/TestProjects/AppWithSubDirs/AppWithSubDirs.csproj index 92aed3cea064c..719f295f06ff4 100644 --- a/src/installer/tests/Assets/TestProjects/AppWithSubDirs/AppWithSubDirs.csproj +++ b/src/installer/tests/Assets/TestProjects/AppWithSubDirs/AppWithSubDirs.csproj @@ -3,8 +3,8 @@ $(NetCoreAppCurrent) Exe - $(TestTargetRid) $(MNAVersion) + $(TestTargetRid) diff --git a/src/installer/tests/Assets/TestProjects/AppWithSubDirs/Directory.Build.props b/src/installer/tests/Assets/TestProjects/AppWithSubDirs/Directory.Build.props new file mode 100644 index 0000000000000..de2e4580b9bea --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/AppWithSubDirs/Directory.Build.props @@ -0,0 +1,9 @@ + + + + false + + + + + diff --git a/src/installer/tests/Assets/TestProjects/AppWithWait/Directory.Build.props b/src/installer/tests/Assets/TestProjects/AppWithWait/Directory.Build.props new file mode 100644 index 0000000000000..de2e4580b9bea --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/AppWithWait/Directory.Build.props @@ -0,0 +1,9 @@ + + + + false + + + + + diff --git a/src/installer/tests/Assets/TestProjects/Directory.Build.targets b/src/installer/tests/Assets/TestProjects/Directory.Build.targets index 099990097e245..71982e083164d 100644 --- a/src/installer/tests/Assets/TestProjects/Directory.Build.targets +++ b/src/installer/tests/Assets/TestProjects/Directory.Build.targets @@ -8,7 +8,10 @@ - + + + + diff --git a/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Directory.Build.props b/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Directory.Build.props new file mode 100644 index 0000000000000..de2e4580b9bea --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Directory.Build.props @@ -0,0 +1,9 @@ + + + + false + + + + + diff --git a/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Program.cs b/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Program.cs index e5ed6805d5c12..56428b14cacad 100644 --- a/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Program.cs +++ b/src/installer/tests/Assets/TestProjects/SingleFileApiTests/Program.cs @@ -1,44 +1,83 @@ using System; +using System.IO; +using System.Reflection; namespace SingleFileApiTests { public class Program { - public static void Main(string[] args) + public static int Main(string[] args) { - switch (args[0]) + for (int i = 0; i < args.Length; i++) { - case "fullyqualifiedname": - var module = typeof(object).Assembly.GetModules()[0]; - Console.WriteLine("FullyQualifiedName: " + module.FullyQualifiedName); - Console.WriteLine("Name: " + module.Name); - return; - - case "cmdlineargs": - Console.WriteLine(Environment.GetCommandLineArgs()[0]); - return; - - case "codebase": - try - { - #pragma warning disable SYSLIB0012 - _ = typeof(Program).Assembly.CodeBase; - #pragma warning restore SYSLIB0012 - } - catch (NotSupportedException) - { - Console.WriteLine("CodeBase NotSupported"); - return; - } - break; - - case "appcontext": - var deps_files = AppContext.GetData("APP_CONTEXT_DEPS_FILES"); - Console.WriteLine("APP_CONTEXT_DEPS_FILES: " + deps_files); - return; + string arg = args[i]; + switch (arg) + { + case "appcontext": + var deps_files = (string)AppContext.GetData("APP_CONTEXT_DEPS_FILES"); + Console.WriteLine("APP_CONTEXT_DEPS_FILES: " + deps_files); + foreach (string deps_file_path in deps_files.Split(";", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + if (!File.Exists(deps_file_path)) + { + Console.WriteLine($"APP_CONTEXT_DEPS_FILES contains path which doesn't exist: '{deps_file_path}'"); + return -1; + } + } + break; + + case "executing_assembly_location": + Console.WriteLine("ExecutingAssembly.Location: " + Assembly.GetExecutingAssembly().Location); + break; + + case "assembly_location": + string assemblyName = args[++i]; + Console.WriteLine(assemblyName + " location: " + Assembly.Load(assemblyName).Location); + break; + + case "cmdlineargs": + Console.WriteLine("Command line args: " + Environment.GetCommandLineArgs()[0]); + break; + + case "codebase": + try + { + #pragma warning disable SYSLIB0012 + var codeBase = typeof(Program).Assembly.CodeBase; + #pragma warning restore SYSLIB0012 + Console.WriteLine("CodeBase " + codeBase); + } + catch (NotSupportedException) + { + Console.WriteLine("CodeBase NotSupported"); + } + break; + + case "fullyqualifiedname": + var module = typeof(object).Assembly.GetModules()[0]; + Console.WriteLine("FullyQualifiedName: " + module.FullyQualifiedName); + Console.WriteLine("Name: " + module.Name); + break; + + case "trusted_platform_assemblies": + Console.WriteLine("TRUSTED_PLATFORM_ASSEMBLIES:"); + foreach (var assemblyPath in ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator)) + { + Console.WriteLine(assemblyPath); + } + break; + + case "basedirectory": + Console.WriteLine("AppContext.BaseDirectory: " + AppContext.BaseDirectory); + break; + + default: + Console.WriteLine("test failure"); + return -1; + } } - Console.WriteLine("test failure"); + return 0; } } } diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs index d6af83a7df819..b932662f8d077 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleExtractToSpecificPath.cs @@ -12,7 +12,7 @@ namespace AppHost.Bundle.Tests { - public class BundleExtractToSpecificPath : IClassFixture + public class BundleExtractToSpecificPath : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -28,8 +28,8 @@ private void Bundle_Extraction_To_Specific_Path_Succeeds() var hostName = BundleHelper.GetHostName(fixture); // Publish the bundle - string singleFile; - Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, options: BundleOptions.BundleNativeBinaries); + UseSingleFileSelfContainedHost(fixture); + Bundler bundler = BundleHelper.BundleApp(fixture, out string singleFile, options: BundleOptions.BundleNativeBinaries); // Verify expected files in the bundle directory var bundleDir = BundleHelper.GetBundleDir(fixture); @@ -65,7 +65,7 @@ private void Bundle_Extraction_To_Specific_Path_Succeeds() private void Bundle_Extraction_To_Relative_Path_Succeeds (string relativePath) { var fixture = sharedTestState.TestFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture, BundleOptions.None); + var singleFile = BundleSelfContainedApp(fixture, BundleOptions.None); // Run the bundled app (extract files to ) Command.Create(singleFile) @@ -85,8 +85,8 @@ private void Bundle_extraction_is_reused() var fixture = sharedTestState.TestFixture.Copy(); // Publish the bundle - string singleFile; - Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries); + UseSingleFileSelfContainedHost(fixture); + Bundler bundler = BundleHelper.BundleApp(fixture, out string singleFile, BundleOptions.BundleNativeBinaries); // Create a directory for extraction. var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); @@ -103,7 +103,6 @@ private void Bundle_extraction_is_reused() .And .HaveStdOutContaining("Hello World"); - var appBaseName = BundleHelper.GetAppBaseName(fixture); var extractDir = BundleHelper.GetExtractionDir(fixture, bundler); extractDir.Refresh(); @@ -136,8 +135,8 @@ private void Bundle_extraction_can_recover_missing_files() var appName = Path.GetFileNameWithoutExtension(hostName); // Publish the bundle - string singleFile; - Bundler bundler = BundleHelper.BundleApp(fixture, out singleFile, BundleOptions.BundleNativeBinaries); + UseSingleFileSelfContainedHost(fixture); + Bundler bundler = BundleHelper.BundleApp(fixture, out string singleFile, BundleOptions.BundleNativeBinaries); // Create a directory for extraction. var extractBaseDir = BundleHelper.GetExtractionRootDir(fixture); @@ -178,19 +177,13 @@ private void Bundle_extraction_can_recover_missing_files() extractDir.Should().HaveFiles(extractedFiles); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, 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)); + TestFixture = PreparePublishedSelfContainedTestProject("StandaloneApp"); } public void Dispose() diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleLocalizedApp.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleLocalizedApp.cs index e713e6f7d8c32..c1c8fbe28d9f8 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleLocalizedApp.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleLocalizedApp.cs @@ -11,7 +11,7 @@ namespace AppHost.Bundle.Tests { - public class BundleLocalizedApp : IClassFixture + public class BundleLocalizedApp : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -24,7 +24,7 @@ public BundleLocalizedApp(SharedTestState fixture) public void Bundled_Localized_App_Run_Succeeds() { var fixture = sharedTestState.TestFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); + var singleFile = BundleSelfContainedApp(fixture); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -42,20 +42,13 @@ public void Bundled_Localized_App_Run_Succeeds() .HaveStdOutContaining("ನಮಸ್ಕಾರ! வணக்கம்! Hello!"); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, IDisposable { public TestProjectFixture TestFixture { get; set; } - public RepoDirectoriesProvider RepoDirectories { get; set; } public SharedTestState() { - RepoDirectories = new RepoDirectoriesProvider(); - - TestFixture = new TestProjectFixture("LocalizedApp", RepoDirectories); - TestFixture - .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages) - .PublishProject(runtime: TestFixture.CurrentRid, - outputDirectory: BundleHelper.GetPublishPath(TestFixture)); + TestFixture = PreparePublishedSelfContainedTestProject("LocalizedApp"); } public void Dispose() diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleProbe.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleProbe.cs index f9d0d04e8b8e9..8679f834a638a 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleProbe.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleProbe.cs @@ -2,16 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; -using Xunit; +using BundleTests.Helpers; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; -using BundleTests.Helpers; -using System.Threading; +using Xunit; namespace AppHost.Bundle.Tests { - public class BundleProbe : IClassFixture + public class BundleProbe : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -40,7 +38,7 @@ private void Bundle_Probe_Not_Passed_For_Non_Single_File_App() private void Bundle_Probe_Passed_For_Single_File_App() { var fixture = sharedTestState.TestFixture.Copy(); - string singleFile = BundleHelper.BundleApp(fixture); + string singleFile = BundleSelfContainedApp(fixture); Command.Create(singleFile, "SingleFile") .CaptureStdErr() @@ -52,19 +50,13 @@ private void Bundle_Probe_Passed_For_Single_File_App() .HaveStdOutContaining("BUNDLE_PROBE OK"); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, IDisposable { public TestProjectFixture TestFixture { get; set; } - public RepoDirectoriesProvider RepoDirectories { get; set; } public SharedTestState() { - RepoDirectories = new RepoDirectoriesProvider(); - TestFixture = new TestProjectFixture("BundleProbeTester", RepoDirectories); - TestFixture - .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages) - .PublishProject(runtime: TestFixture.CurrentRid, - outputDirectory: BundleHelper.GetPublishPath(TestFixture)); + TestFixture = PreparePublishedSelfContainedTestProject("BundleProbeTester"); } public void Dispose() diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs index 006df6e3dc518..22e27c578523c 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleRename.cs @@ -3,16 +3,16 @@ using System; using System.IO; -using Xunit; +using System.Threading; +using BundleTests.Helpers; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; using Microsoft.NET.HostModel.Bundle; -using BundleTests.Helpers; -using System.Threading; +using Xunit; namespace AppHost.Bundle.Tests { - public class BundleRename : IClassFixture + public class BundleRename : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -29,7 +29,7 @@ private void Bundle_can_be_renamed_while_running(bool testExtraction) { var fixture = sharedTestState.TestFixture.Copy(); BundleOptions options = testExtraction ? BundleOptions.BundleAllContent : BundleOptions.None; - string singleFile = BundleHelper.BundleApp(fixture, options); + string singleFile = BundleSelfContainedApp(fixture, options); string outputDir = Path.GetDirectoryName(singleFile); string renameFile = Path.Combine(outputDir, Path.GetRandomFileName()); string waitFile = Path.Combine(outputDir, "wait"); @@ -63,19 +63,13 @@ private void Bundle_can_be_renamed_while_running(bool testExtraction) .HaveStdOutContaining("Hello World!"); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, IDisposable { public TestProjectFixture TestFixture { get; set; } - public RepoDirectoriesProvider RepoDirectories { get; set; } public SharedTestState() { - RepoDirectories = new RepoDirectoriesProvider(); - TestFixture = new TestProjectFixture("AppWithWait", RepoDirectories); - TestFixture - .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages) - .PublishProject(runtime: TestFixture.CurrentRid, - outputDirectory: BundleHelper.GetPublishPath(TestFixture)); + TestFixture = PreparePublishedSelfContainedTestProject("AppWithWait"); } public void Dispose() diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleTestBase.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleTestBase.cs new file mode 100644 index 0000000000000..e01d71d8dcb35 --- /dev/null +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundleTestBase.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +using System; +using System.IO; +using BundleTests.Helpers; +using Microsoft.DotNet.CoreSetup.Test; +using Microsoft.NET.HostModel.AppHost; +using Microsoft.NET.HostModel.Bundle; + +namespace AppHost.Bundle.Tests +{ + public abstract class BundleTestBase + { + // This helper is used in lieu of SDK support for publishing apps using the singlefilehost. + // It replaces the apphost with singlefilehost, and along with appropriate app.dll updates in the host. + // For now, we leave behind the hostpolicy and hostfxr DLLs in the publish directory, because + // removing them requires deps.json update. + public static string UseSingleFileSelfContainedHost(TestProjectFixture testFixture) + { + var singleFileHost = Path.Combine( + testFixture.RepoDirProvider.HostArtifacts, + RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("singlefilehost")); + var publishedHostPath = BundleHelper.GetHostPath(testFixture); + HostWriter.CreateAppHost(singleFileHost, + publishedHostPath, + BundleHelper.GetAppPath(testFixture)); + return publishedHostPath; + } + + public static string UseFrameworkDependentHost(TestProjectFixture testFixture) + { + var appHost = Path.Combine( + testFixture.RepoDirProvider.HostArtifacts, + RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("apphost")); + var publishedHostPath = BundleHelper.GetHostPath(testFixture); + HostWriter.CreateAppHost(appHost, + publishedHostPath, + BundleHelper.GetAppPath(testFixture)); + return publishedHostPath; + } + + public static string BundleSelfContainedApp( + TestProjectFixture testFixture, + BundleOptions options = BundleOptions.None, + Version targetFrameworkVersion = null) + { + UseSingleFileSelfContainedHost(testFixture); + return BundleHelper.BundleApp(testFixture, options, targetFrameworkVersion); + } + + public abstract class SharedTestStateBase + { + public RepoDirectoriesProvider RepoDirectories { get; set; } + + public SharedTestStateBase() + { + RepoDirectories = new RepoDirectoriesProvider(); + } + + public TestProjectFixture PreparePublishedSelfContainedTestProject(string projectName) + { + var testFixture = new TestProjectFixture(projectName, RepoDirectories); + testFixture + .EnsureRestoredForRid(testFixture.CurrentRid, RepoDirectories.CorehostPackages) + .PublishProject(runtime: testFixture.CurrentRid, + outputDirectory: BundleHelper.GetPublishPath(testFixture)); + + return testFixture; + } + } + } +} diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs index e336de81dd5fa..503f755ecb296 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs @@ -1,16 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using BundleTests.Helpers; using Microsoft.DotNet.Cli.Build.Framework; -using Microsoft.NET.HostModel.Bundle; using Microsoft.DotNet.CoreSetup.Test; -using System; +using Microsoft.NET.HostModel.Bundle; using Xunit; namespace AppHost.Bundle.Tests { - public class BundledAppWithSubDirs : IClassFixture + public class BundledAppWithSubDirs : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -19,11 +19,15 @@ public BundledAppWithSubDirs(SharedTestState fixture) sharedTestState = fixture; } - private void RunTheApp(string path) + private void RunTheApp(string path, TestProjectFixture fixture) { Command.Create(path) + .EnvironmentVariable("COREHOST_TRACE", "1") .CaptureStdErr() .CaptureStdOut() + .EnvironmentVariable("DOTNET_ROOT", fixture.BuiltDotnet.BinPath) + .EnvironmentVariable("DOTNET_ROOT(x86)", fixture.BuiltDotnet.BinPath) + .EnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0") .Execute() .Should() .Pass() @@ -38,13 +42,14 @@ private void RunTheApp(string path) public void Bundled_Framework_dependent_App_Run_Succeeds(BundleOptions options) { var fixture = sharedTestState.TestFrameworkDependentFixture.Copy(); + UseFrameworkDependentHost(fixture); var singleFile = BundleHelper.BundleApp(fixture, options); // Run the bundled app (extract files) - RunTheApp(singleFile); + RunTheApp(singleFile, fixture); // Run the bundled app again (reuse extracted files) - RunTheApp(singleFile); + RunTheApp(singleFile, fixture); } [InlineData(BundleOptions.None)] @@ -57,10 +62,10 @@ public void Bundled_Self_Contained_App_Run_Succeeds(BundleOptions options) var singleFile = BundleHelper.BundleApp(fixture, options); // Run the bundled app (extract files) - RunTheApp(singleFile); + RunTheApp(singleFile, fixture); // Run the bundled app again (reuse extracted files) - RunTheApp(singleFile); + RunTheApp(singleFile, fixture); } [InlineData(BundleOptions.None)] @@ -73,25 +78,23 @@ public void Bundled_With_Empty_File_Succeeds(BundleOptions options) var singleFile = BundleHelper.BundleApp(fixture, options); // Run the app - RunTheApp(singleFile); + RunTheApp(singleFile, fixture); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, IDisposable { public TestProjectFixture TestFrameworkDependentFixture { get; set; } public TestProjectFixture TestSelfContainedFixture { get; set; } public TestProjectFixture TestAppWithEmptyFileFixture { get; set; } - public RepoDirectoriesProvider RepoDirectories { get; set; } public SharedTestState() { - RepoDirectories = new RepoDirectoriesProvider(); - TestFrameworkDependentFixture = new TestProjectFixture("AppWithSubDirs", RepoDirectories); BundleHelper.AddLongNameContentToAppWithSubDirs(TestFrameworkDependentFixture); TestFrameworkDependentFixture .EnsureRestoredForRid(TestFrameworkDependentFixture.CurrentRid, RepoDirectories.CorehostPackages) .PublishProject(runtime: TestFrameworkDependentFixture.CurrentRid, + selfContained: false, outputDirectory: BundleHelper.GetPublishPath(TestFrameworkDependentFixture)); TestSelfContainedFixture = new TestProjectFixture("AppWithSubDirs", RepoDirectories); diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/HammerServiceTest.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/HammerServiceTest.cs index 5c0206573c47d..77c657433809b 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/HammerServiceTest.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/HammerServiceTest.cs @@ -11,7 +11,7 @@ namespace AppHost.Bundle.Tests { - public class HammerServiceTest : IClassFixture + public class HammerServiceTest : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -38,7 +38,7 @@ private void SingleFile_Apps_Are_Serviced() // Annotate the app as servicible, and then publish to a single-file. string depsjson = BundleHelper.GetDepsJsonPath(fixture); File.WriteAllText(depsjson, File.ReadAllText(depsjson).Replace("\"serviceable\": false", "\"serviceable\": true")); - var singleFile = BundleHelper.BundleApp(fixture); + var singleFile = BundleSelfContainedApp(fixture); // Create the servicing directory, and copy the servived DLL from service fixture to the servicing directory. var serviceBasePath = Path.Combine(fixture.TestProject.ProjectDirectory, "coreservicing"); @@ -68,20 +68,15 @@ private void SingleFile_Apps_Are_Serviced() .HaveStdOutContaining("Hi Bengaluru!"); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, IDisposable { public TestProjectFixture TestFixture { get; set; } public TestProjectFixture ServiceFixture { get; set; } - public RepoDirectoriesProvider RepoDirectories { get; set; } public SharedTestState() { RepoDirectories = new RepoDirectoriesProvider(); - TestFixture = new TestProjectFixture("HammerServiceApp", RepoDirectories); - TestFixture - .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages) - .PublishProject(runtime: TestFixture.CurrentRid, - outputDirectory: BundleHelper.GetPublishPath(TestFixture)); + TestFixture = PreparePublishedSelfContainedTestProject("HammerServiceApp"); ServiceFixture = new TestProjectFixture("ServicedLocation", RepoDirectories, assemblyName: "Location"); ServiceFixture diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/NetCoreApp3CompatModeTests.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/NetCoreApp3CompatModeTests.cs new file mode 100644 index 0000000000000..dc64be2b47dbc --- /dev/null +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/NetCoreApp3CompatModeTests.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +using System; +using System.IO; +using System.Linq; +using BundleTests.Helpers; +using Microsoft.DotNet.Cli.Build.Framework; +using Microsoft.DotNet.CoreSetup.Test; +using Microsoft.NET.HostModel.Bundle; +using Xunit; + +namespace AppHost.Bundle.Tests +{ + public class NetCoreApp3CompatModeTests : BundleTestBase, IClassFixture + { + private SharedTestState sharedTestState; + + public NetCoreApp3CompatModeTests(SharedTestState fixture) + { + sharedTestState = fixture; + } + + [Fact] + public void Bundle_Is_Extracted() + { + var fixture = sharedTestState.SingleFileTestFixture.Copy(); + UseSingleFileSelfContainedHost(fixture); + Bundler bundler = BundleHelper.BundleApp(fixture, out string singleFile, BundleOptions.BundleAllContent); + var extractionBaseDir = BundleHelper.GetExtractionRootDir(fixture); + + Command.Create(singleFile, "executing_assembly_location trusted_platform_assemblies assembly_location System.Console") + .CaptureStdOut() + .CaptureStdErr() + .EnvironmentVariable(BundleHelper.DotnetBundleExtractBaseEnvVariable, extractionBaseDir.FullName) + .Execute() + .Should() + .Pass() + // Validate that the main assembly is running from disk (and not from bundle) + .And.HaveStdOutContaining("ExecutingAssembly.Location: " + extractionBaseDir.FullName) + // Validate that TPA contains at least one framework assembly from the extraction directory + .And.HaveStdOutContaining("System.Runtime.dll") + // Validate that framework assembly is actually loaded from the extraction directory + .And.HaveStdOutContaining("System.Console location: " + extractionBaseDir.FullName); + + var extractionDir = BundleHelper.GetExtractionDir(fixture, bundler); + var bundleFiles = BundleHelper.GetBundleDir(fixture).GetFiles().Select(file => file.Name).ToArray(); + var publishedFiles = Directory.GetFiles(BundleHelper.GetPublishPath(fixture), searchPattern: "*", searchOption: SearchOption.AllDirectories) + .Select(file => Path.GetFileName(file)) + .Except(bundleFiles) + .ToArray(); + extractionDir.Should().HaveFiles(publishedFiles); + } + + public class SharedTestState : SharedTestStateBase, IDisposable + { + public TestProjectFixture SingleFileTestFixture { get; set; } + + public SharedTestState() + { + SingleFileTestFixture = PreparePublishedSelfContainedTestProject("SingleFileApiTests"); + } + + public void Dispose() + { + SingleFileTestFixture.Dispose(); + } + } + } +} diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/SingleFileApiTests.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/SingleFileApiTests.cs index 5d5f6b8c786d4..37f8b0fff088f 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/SingleFileApiTests.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/SingleFileApiTests.cs @@ -1,12 +1,14 @@ using System; +using System.IO; using BundleTests.Helpers; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; +using Microsoft.NET.HostModel.Bundle; using Xunit; namespace AppHost.Bundle.Tests { - public class SingleFileApiTests : IClassFixture + public class SingleFileApiTests : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -16,77 +18,56 @@ public SingleFileApiTests(SharedTestState fixture) } [Fact] - public void FullyQualifiedName() + public void SelfContained_SingleFile_APITests() { var fixture = sharedTestState.TestFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); + var singleFile = BundleSelfContainedApp(fixture); - Command.Create(singleFile, "fullyqualifiedname") + Command.Create(singleFile, "fullyqualifiedname codebase appcontext cmdlineargs executing_assembly_location basedirectory") .CaptureStdErr() .CaptureStdOut() .Execute() .Should() .Pass() - .And - .HaveStdOutContaining("FullyQualifiedName: " + - Environment.NewLine + - "Name: "); - } - - [Fact] - public void CodeBaseThrows() - { - var fixture = sharedTestState.TestFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); - - Command.Create(singleFile, "codebase") - .CaptureStdErr() - .CaptureStdOut() - .Execute() - .Should() - .Pass() - .And - .HaveStdOutContaining("CodeBase NotSupported"); + .And.HaveStdOutContaining("FullyQualifiedName: ") + .And.HaveStdOutContaining("Name: ") + .And.HaveStdOutContaining("CodeBase NotSupported") + .And.NotHaveStdOutContaining("SingleFileApiTests.deps.json") + .And.NotHaveStdOutContaining("Microsoft.NETCore.App.deps.json") + // For single-file, Environment.GetCommandLineArgs[0] should return the file path of the host. + .And.HaveStdOutContaining("Command line args: " + singleFile) + .And.HaveStdOutContaining("ExecutingAssembly.Location: " + Environment.NewLine) + .And.HaveStdOutContaining("AppContext.BaseDirectory: " + Path.GetDirectoryName(singleFile)); } [Fact] - public void AppContext_Deps_Files_Bundled_Self_Contained() + public void SelfContained_NetCoreApp3_CompatMode_SingleFile_APITests() { var fixture = sharedTestState.TestFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); + var singleFile = BundleSelfContainedApp(fixture, BundleOptions.BundleAllContent); + var extractionBaseDir = BundleHelper.GetExtractionRootDir(fixture); - Command.Create(singleFile, "appcontext") + Command.Create(singleFile, "fullyqualifiedname codebase appcontext cmdlineargs executing_assembly_location basedirectory") .CaptureStdErr() .CaptureStdOut() + .EnvironmentVariable(BundleHelper.DotnetBundleExtractBaseEnvVariable, extractionBaseDir.FullName) .Execute() .Should() .Pass() - .And - .NotHaveStdOutContaining("SingleFileApiTests.deps.json") - .And - .NotHaveStdOutContaining("Microsoft.NETCore.App.deps.json"); - } - - [Fact] - public void GetEnvironmentArgs_0_Returns_Bundled_Executable_Path() - { - var fixture = sharedTestState.TestFixture.Copy(); - var singleFile = BundleHelper.BundleApp(fixture); - - // For single-file, Environment.GetCommandLineArgs[0] - // should return the file path of the host. - Command.Create(singleFile, "cmdlineargs") - .CaptureStdErr() - .CaptureStdOut() - .Execute() - .Should() - .Pass() - .And - .HaveStdOutContaining(singleFile); + .And.HaveStdOutContaining(Path.DirectorySeparatorChar + "System.Private.CoreLib.dll") // In extraction directory + .And.HaveStdOutContaining("System.Private.CoreLib.dll") // In extraction directory + .And.NotHaveStdOutContaining("CodeBase NotSupported") // CodeBase should point to extraction directory + .And.HaveStdOutContaining("SingleFileApiTests.dll") + .And.HaveStdOutContaining("SingleFileApiTests.deps.json") // The app's .deps.json should be available + .And.NotHaveStdOutContaining("Microsoft.NETCore.App.deps.json") // No framework - it's self-contained + // For single-file, Environment.GetCommandLineArgs[0] should return the file path of the host. + .And.HaveStdOutContaining("Command line args: " + singleFile) + .And.HaveStdOutContaining("ExecutingAssembly.Location: " + extractionBaseDir.FullName) // Should point to the app's dll + .And.HaveStdOutContaining("AppContext.BaseDirectory: " + extractionBaseDir.FullName); // Should point to the extraction directory } [Fact] - public void GetEnvironmentArgs_0_Non_Bundled_App() + public void GetCommandLineArgs_0_Non_Bundled_App() { var fixture = sharedTestState.TestFixture.Copy(); var dotnet = fixture.BuiltDotnet; @@ -104,18 +85,13 @@ public void GetEnvironmentArgs_0_Non_Bundled_App() .HaveStdOutContaining(appPath); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, IDisposable { public TestProjectFixture TestFixture { get; set; } - public RepoDirectoriesProvider RepoDirectories { get; set; } public SharedTestState() { - RepoDirectories = new RepoDirectoriesProvider(); - TestFixture = new TestProjectFixture("SingleFileApiTests", RepoDirectories); - TestFixture - .EnsureRestoredForRid(TestFixture.CurrentRid, RepoDirectories.CorehostPackages) - .PublishProject(runtime: TestFixture.CurrentRid, outputDirectory: BundleHelper.GetPublishPath(TestFixture)); + TestFixture = PreparePublishedSelfContainedTestProject("SingleFileApiTests"); } public void Dispose() diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/StaticHost.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/StaticHost.cs index b1111e9c9b027..71d365e93bb65 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/StaticHost.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/StaticHost.cs @@ -1,19 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using BundleTests.Helpers; +using System; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.DotNet.CoreSetup.Test; -using Microsoft.NET.HostModel.AppHost; -using Microsoft.NET.HostModel.Bundle; -using System; -using System.IO; -using System.Threading; using Xunit; namespace AppHost.Bundle.Tests { - public class StaticHost : IClassFixture + public class StaticHost : BundleTestBase, IClassFixture { private SharedTestState sharedTestState; @@ -22,27 +17,12 @@ public StaticHost(SharedTestState fixture) sharedTestState = fixture; } - // This helper is used in lieu of SDK support for publishing apps using the singlefilehost. - // It replaces the apphost with singlefilehost, and along with appropriate app.dll updates in the host. - // For now, we leave behind the hostpolicy and hostfxr DLLs in the publish directory, because - // removing them requires deps.json update. - void ReplaceApphostWithStaticHost(TestProjectFixture fixture) - { - var staticHost = Path.Combine(fixture.RepoDirProvider.HostArtifacts, - RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("singlefilehost")); - HostWriter.CreateAppHost(staticHost, - BundleHelper.GetHostPath(fixture), - BundleHelper.GetAppPath(fixture)); - - } - [Fact] private void Can_Run_App_With_StatiHost() { var fixture = sharedTestState.TestFixture.Copy(); - var appExe = BundleHelper.GetHostPath(fixture); - ReplaceApphostWithStaticHost(fixture); + var appExe = UseSingleFileSelfContainedHost(fixture); Command.Create(appExe) .CaptureStdErr() @@ -59,9 +39,7 @@ private void Can_Run_SingleFile_App_With_StatiHost() { var fixture = sharedTestState.TestFixture.Copy(); - ReplaceApphostWithStaticHost(fixture); - - string singleFile = BundleHelper.BundleApp(fixture); + string singleFile = BundleSelfContainedApp(fixture); Command.Create(singleFile) .CaptureStdErr() @@ -73,19 +51,13 @@ private void Can_Run_SingleFile_App_With_StatiHost() .HaveStdOutContaining("Hello World"); } - public class SharedTestState : IDisposable + public class SharedTestState : SharedTestStateBase, 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)); + TestFixture = PreparePublishedSelfContainedTestProject("StandaloneApp"); } public void Dispose()