diff --git a/CHANGELOG.md b/CHANGELOG.md index a76cc7a15..c5a6dff86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.10.7] - Unreleased ### Added +- #945: The `load` command will unpack the tarball (or copy the non-tarball source) into the standard unpacking directory `$System.Util.DataDirectory()/ipm///` just like the install command - #992: Implement automatic history purge logic - #973: Enables CORS and JWT configuration for WebApplications in module.xml - #1110: Add `iriscli` and `ipm` container utility scripts that are auto-installed to `~/.local/bin/` and `~/bin/` so they work both inside and outside of containers (Unix/Linux only) diff --git a/src/cls/IPM/General/Archive.cls b/src/cls/IPM/General/Archive.cls index 292f9a3dc..17f526e0f 100644 --- a/src/cls/IPM/General/Archive.cls +++ b/src/cls/IPM/General/Archive.cls @@ -53,9 +53,8 @@ ClassMethod Extract( quit } if '##class(%File).DirectoryExists(pTargetDirectory) { - set tResult = ##class(%File).CreateDirectoryChain(pTargetDirectory,.tReturn) - if 'tResult { - set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory chain %1: %2",pTargetDirectory,tReturn)) + set tSC = ##class(%IPM.Utils.File).CreateDirectoryChain(pTargetDirectory) + if $$$ISERR(tSC) { quit } } diff --git a/src/cls/IPM/Lifecycle/Base.cls b/src/cls/IPM/Lifecycle/Base.cls index 5a3a6a528..c412fcfc0 100644 --- a/src/cls/IPM/Lifecycle/Base.cls +++ b/src/cls/IPM/Lifecycle/Base.cls @@ -1153,11 +1153,8 @@ Method %Export( } if '##class(%File).DirectoryExists(pTargetDirectory) { - kill %objlasterror - set tCreated = ##class(%File).CreateDirectoryChain(pTargetDirectory,.tReturnValue) - if 'tCreated { - set tLastErr = $get(%objlasterror) - set tSC = $$$EMBEDSC($$$ERROR($$$GeneralError,$$$FormatText("Error creating directory %1: %2",pTargetDirectory,tReturnValue)),tLastErr) + set tSC = ##class(%IPM.Utils.File).CreateDirectoryChain(pTargetDirectory) + if $$$ISERR(tSC) { quit } } @@ -1956,12 +1953,7 @@ Method ExportSingleModule( set tSC = $$$OK set tDirectory = ##class(%File).GetDirectory(tExportPath,1) if '##class(%File).DirectoryExists(tDirectory) { - set tGood = ##class(%File).CreateDirectoryChain(tDirectory,.tReturn) - if 'tGood { - set tLastErr = $get(%objlasterror) - set tSC = $$$EMBEDSC($$$ERROR($$$GeneralError,$$$FormatText("Error creating directory '%1': %2",tDirectory,tReturn)),tLastErr) - $$$ThrowOnError(tSC) - } + $$$ThrowOnError(##class(%IPM.Utils.File).CreateDirectoryChain(tDirectory)) write:pVerbose !,"Created ",tDirectory } if ##class(%File).DirectoryExists(tSourcePath) { @@ -1981,7 +1973,7 @@ Method ExportSingleModule( } } elseif (tExt = "CLS") || (tExt = "DFI") || (tExt = "PRJ") || ($zconvert($extract(tFullPath,$length(tFullPath)-2,*),"l")="xml") || (##class(%RoutineMgr).UserType(tFullResourceName)) { if '##class(%File).Exists(tFullPath) { - do ##class(%File).CreateDirectoryChain(##class(%File).GetDirectory(tFullPath)) + $$$ThrowOnError(##class(%IPM.Utils.File).CreateDirectoryChain(##class(%File).GetDirectory(tFullPath))) } set tSC = $$Export^%occXMLExport(tFullPath,"-d /diffexport/createdirs",tFullResourceName) $$$ThrowOnError(tSC) diff --git a/src/cls/IPM/Lifecycle/Module.cls b/src/cls/IPM/Lifecycle/Module.cls index 88b800cc7..900c6cb9d 100644 --- a/src/cls/IPM/Lifecycle/Module.cls +++ b/src/cls/IPM/Lifecycle/Module.cls @@ -148,8 +148,8 @@ Method %Package(ByRef pParams) As %Status // create temporary subdirectory to prevent clashes set randomDir = $translate($system.Encryption.Base64Encode($system.Encryption.GenCryptRand(6)),"+/=") set tExportSubDirectory = ##class(%Library.File).NormalizeDirectory(tExportDirectory) _ randomDir _ "/" - if '##class(%File).CreateDirectoryChain(tExportSubDirectory,.tReturn) { - set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory chain %1: %2",tExportSubDirectory,tReturn)) + set tSC = ##class(%IPM.Utils.File).CreateDirectoryChain(tExportSubDirectory) + if $$$ISERR(tSC) { quit } } diff --git a/src/cls/IPM/Lifecycle/StudioProject/XDataArchive.cls b/src/cls/IPM/Lifecycle/StudioProject/XDataArchive.cls index d24f0dd72..6e83d8ed3 100644 --- a/src/cls/IPM/Lifecycle/StudioProject/XDataArchive.cls +++ b/src/cls/IPM/Lifecycle/StudioProject/XDataArchive.cls @@ -156,7 +156,7 @@ ClassMethod CreateDirectoryChain(pName As %String) As %Status { set tSC = $$$OK if '##class(%Library.File).CreateDirectoryChain(pName,.tReturn) { - set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory chain %1: %2",pName,$zutil(209,tReturn))) + set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory chain %1: %2",pName,$zutil(209,$select(tReturn<0:-tReturn,1:tReturn)))) } quit tSC } diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls index 3871ecdbf..bc8c12a16 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -2274,7 +2274,7 @@ ClassMethod LoadFromRepo( { set slash=$select($zversion(1)=3:"/",1:"\") set TempDir = ##class(%File).GetDirectory(##class(%File).GetDirectory($zutil(86))_"mgr"_slash_"Temp"_slash_$translate($ztimestamp,".,")_slash) - $$$ThrowOnError(##class(%File).CreateDirectoryChain(TempDir)) + $$$ThrowOnError(##class(%IPM.Utils.File).CreateDirectoryChain(TempDir)) set:$extract(tDirectoryName,*)="/" tDirectoryName=$extract(tDirectoryName,1,*-1) set RepoName=$piece($piece($piece(tDirectoryName,"/",*),".git")," ") set tCmd="cd "_TempDir_" && git clone "_tDirectoryName @@ -2350,7 +2350,8 @@ ClassMethod LoadInternal( } set extension = $$$lcase($piece(tDirectoryName,".",*)) if ##class(%File).Exists(tDirectoryName) && ((extension="tgz") || (extension="tar.gz")) { - set tTargetDirectory = $$$FileTempDirSys + set tTempExtractionRoot = $$$FileTempDirSys + set tTargetDirectory = tTempExtractionRoot if $get(pCommandInfo("data", "Verbose")) { write !,"Extracting archive to ",tTargetDirectory } @@ -2382,6 +2383,24 @@ ClassMethod LoadInternal( // When loading a module from a local folder, there might be a /.modules/ folder containining dependencies. // It's easier to configure a temporary repository than to handle this case in the dependency resolution code. set tTargetDirectory = $get(tTargetDirectory, tDirectoryName) + + // Copy source to the standard module location (DataDirectory/ipm///) unless in developer mode, + // where the developer works on files in-place. + if '$get(tParams("DeveloperMode"), 0) { + set tModuleObj = ##class(%IPM.Utils.Module).GetModuleObjectFromPath(tTargetDirectory, .tFound) + set tFinalDirectory = ##class(%Library.File).NormalizeDirectory( + ##class(%SYSTEM.Util).DataDirectory() _ "ipm/" _ tModuleObj.Name _ "/" _ tModuleObj.VersionString) + if ##class(%File).DirectoryExists(tFinalDirectory) { + $$$ThrowOnError(##class(%IPM.Utils.File).RemoveDirectoryTree(tFinalDirectory)) + } + $$$ThrowOnError(##class(%IPM.Utils.File).CreateDirectoryChain(tFinalDirectory)) + $$$ThrowOnError(##class(%IPM.Utils.File).CopyDir(tTargetDirectory, tFinalDirectory, 0)) + if $get(tTempExtractionRoot) '= "" { + $$$ThrowOnError(##class(%IPM.Utils.File).RemoveDirectoryTree(tTempExtractionRoot)) + } + set tTargetDirectory = tFinalDirectory + } + set dotModules = ##class(%File).NormalizeDirectory(".modules", tTargetDirectory) set tmpRepoMgr = ##class(%IPM.General.TempLocalRepoManager).%New(dotModules, 1) set tSC = ##class(%IPM.Utils.Module).LoadNewModule(tTargetDirectory, .tParams, , pLog) diff --git a/src/cls/IPM/Repo/Oras/PublishService.cls b/src/cls/IPM/Repo/Oras/PublishService.cls index 78d1e99c8..3c701fbda 100644 --- a/src/cls/IPM/Repo/Oras/PublishService.cls +++ b/src/cls/IPM/Repo/Oras/PublishService.cls @@ -18,10 +18,7 @@ Method PublishModule(pModule As %IPM.Repo.Remote.ModuleInfo) As %Status set tempDirectory = ##class(%File).NormalizeDirectory(tDir _ "/" _ repo _ "/" _ tag _ "/") #; Create temp directory - set tCreated = ##class(%File).CreateDirectoryChain(tempDirectory,.tReturnValue) - if 'tCreated { - $$$ThrowStatus($$$ERROR($$$GeneralError, $$$FormatText("Error creating directory %1: %2", tempDirectory, tReturnValue))) - } + $$$ThrowOnError(##class(%IPM.Utils.File).CreateDirectoryChain(tempDirectory)) #; Create tgz file in temp directory set tFileBinStream = ##class(%Stream.FileBinary).%New() diff --git a/src/cls/IPM/ResourceProcessor/ArtifactoryTarball.cls b/src/cls/IPM/ResourceProcessor/ArtifactoryTarball.cls index 550955dcc..67ff11e05 100644 --- a/src/cls/IPM/ResourceProcessor/ArtifactoryTarball.cls +++ b/src/cls/IPM/ResourceProcessor/ArtifactoryTarball.cls @@ -267,9 +267,7 @@ ClassMethod RetrieveArtifact( // Create directory if it doesn't exist if '##class(%File).DirectoryExists(downloadDirectory) { - if '##class(%File).CreateDirectoryChain(downloadDirectory) { - $$$ThrowStatus($$$ERROR($$$GeneralError, "Unable to create destination directory: "_downloadDirectory)) - } + $$$ThrowOnError(##class(%IPM.Utils.File).CreateDirectoryChain(downloadDirectory)) } set localPath = ##class(%File).NormalizeFilename(artifactName, downloadDirectory) diff --git a/src/cls/IPM/ResourceProcessor/FileCopy.cls b/src/cls/IPM/ResourceProcessor/FileCopy.cls index 24874799f..22976ea2a 100644 --- a/src/cls/IPM/ResourceProcessor/FileCopy.cls +++ b/src/cls/IPM/ResourceProcessor/FileCopy.cls @@ -158,8 +158,8 @@ Method DoCopy( do ..NormalizeNames(.tSource, .tTarget, .tTargetDir, .copyAsFile) if '##class(%File).DirectoryExists(tTargetDir) { - if '##class(%File).CreateDirectoryChain(tTargetDir,.tReturn) { - set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory %1: %2",tTargetDir,$zutil(209,tReturn))) + set tSC = ##class(%IPM.Utils.File).CreateDirectoryChain(tTargetDir) + if $$$ISERR(tSC) { quit } } diff --git a/src/cls/IPM/ResourceProcessor/PythonWheel.cls b/src/cls/IPM/ResourceProcessor/PythonWheel.cls index 0cb67a5a4..399787d82 100644 --- a/src/cls/IPM/ResourceProcessor/PythonWheel.cls +++ b/src/cls/IPM/ResourceProcessor/PythonWheel.cls @@ -94,8 +94,9 @@ Method OnExportItem( return $$$ERROR($$$GeneralError, "File """_dir_""" exists and is not a directory. Failed to export item: "_..Name) } if '##class(%File).DirectoryExists(dir) { - if '##class(%File).CreateDirectoryChain(dir, .return) { - return $$$ERROR($$$GeneralError, "Failed to create directory "_dir_", OS returned code: "_-return) + set sc = ##class(%IPM.Utils.File).CreateDirectoryChain(dir) + if $$$ISERR(sc) { + return sc } } if verbose { diff --git a/src/cls/IPM/ResourceProcessor/Test.cls b/src/cls/IPM/ResourceProcessor/Test.cls index a146f4b5b..285ff1b93 100644 --- a/src/cls/IPM/ResourceProcessor/Test.cls +++ b/src/cls/IPM/ResourceProcessor/Test.cls @@ -220,7 +220,7 @@ Method OnPhase( } set outputDir = ##class(%File).GetDirectory(outputFile) if outputDir '= "" { - $$$ThrowOnError(##class(%File).CreateDirectoryChain(outputDir)) + $$$ThrowOnError(##class(%IPM.Utils.File).CreateDirectoryChain(outputDir)) } set tSC = $classmethod(outputClass,"ToFile",outputFile) $$$ThrowOnError(tSC) diff --git a/src/cls/IPM/Utils/File.cls b/src/cls/IPM/Utils/File.cls index bda659460..12b6229b9 100644 --- a/src/cls/IPM/Utils/File.cls +++ b/src/cls/IPM/Utils/File.cls @@ -10,7 +10,7 @@ ClassMethod CreateDirectoryChain(pName As %String) As %Status { set tSC = $$$OK if '##class(%Library.File).CreateDirectoryChain(pName,.tReturn) { - set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory chain %1: %2",pName,$zutil(209,tReturn))) + set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory chain %1: %2",pName,$zutil(209,$select(tReturn<0:-tReturn,1:tReturn)))) } quit tSC } diff --git a/src/cls/IPM/Utils/FileBinaryTar.cls b/src/cls/IPM/Utils/FileBinaryTar.cls index 912e10614..430d75013 100644 --- a/src/cls/IPM/Utils/FileBinaryTar.cls +++ b/src/cls/IPM/Utils/FileBinaryTar.cls @@ -155,7 +155,7 @@ Method ExtractTo(pDest As %String) As %Status if ..name'="" { if ..typeflag=5 { set fullPath = ##class(%File).NormalizeDirectory(..name, pDest) - set sc = ##class(%File).CreateDirectoryChain(fullPath) + set sc = ##class(%IPM.Utils.File).CreateDirectoryChain(fullPath) if $$$ISERR(sc) { return sc } @@ -164,8 +164,9 @@ Method ExtractTo(pDest As %String) As %Status set path = pDest_..name set directory = ##class(%File).GetDirectory(path) if '##class(%File).DirectoryExists(directory) { - if '##class(%File).CreateDirectoryChain(directory, .ret) { - return $$$ERROR($$$GeneralError, $$$FormatText("Error creating directory chain %1: %2", directory, ret)) + set sc = ##class(%IPM.Utils.File).CreateDirectoryChain(directory) + if $$$ISERR(sc) { + return sc } } #; no idea, why it's needed, but IRIS.DAT filename not allowed diff --git a/src/cls/IPM/Utils/Module.cls b/src/cls/IPM/Utils/Module.cls index 56bf4059d..96a93c3f2 100644 --- a/src/cls/IPM/Utils/Module.cls +++ b/src/cls/IPM/Utils/Module.cls @@ -235,9 +235,8 @@ ClassMethod LoadModuleFromArchive( quit } } - set tCreated = ##class(%File).CreateDirectoryChain(tTargetDirectory,.tReturnValue) - if 'tCreated { - set tSC = $$$ERROR($$$GeneralError,$$$FormatText("Error creating directory %1: %2",tTargetDirectory,tReturnValue)) + set tSC = ##class(%IPM.Utils.File).CreateDirectoryChain(tTargetDirectory) + if $$$ISERR(tSC) { quit } set tTargetDirectory = ##class(%File).NormalizeFilenameWithSpaces(tTargetDirectory) diff --git a/tests/integration_tests/Test/PM/Integration/FileBinaryTar.cls b/tests/integration_tests/Test/PM/Integration/FileBinaryTar.cls index 6870e3d32..86fe7e30c 100644 --- a/tests/integration_tests/Test/PM/Integration/FileBinaryTar.cls +++ b/tests/integration_tests/Test/PM/Integration/FileBinaryTar.cls @@ -5,36 +5,89 @@ Class Test.PM.Integration.FileBinaryTar Extends %UnitTest.TestCase Method TestPackageAndExtract() { - Set tSC = $$$OK - Try { - Set tTestRoot = ##class(%File).NormalizeDirectory($Get(^UnitTestRoot)) - - Set tModuleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(tTestRoot)_"/_data/simple-module/") - Set tSC = ##class(%IPM.Main).Shell("load "_tModuleDir) - Do $$$AssertStatusOK(tSC,"Loaded SimpleModule module successfully.") - - Set tempDir = ##class(%Library.File).TempFilename()_"dir" - Set tSC = ##class(%IPM.Main).Shell("simplemodule package -only -DPath="_tempDir) - Do $$$AssertStatusOK(tSC,"Packaged SimpleModule successfully.") - - Set outFile = ##class(%Library.File).TempFilename() - Set outDir = ##class(%Library.File).NormalizeDirectory(##class(%Library.File).TempFilename()_"dir-out") - Do ##class(%Library.File).CreateDirectoryChain(outDir) - Do $$$AssertEquals($zf(-100,"/STDOUT="""_outFile_"""/STDERR="""_outFile_"""","tar","-xvf",tempDir_".tgz","-C",outDir),0) - - Do $$$AssertNotTrue(##class(%File).DirectoryExists(outDir_"src/cls/Test/Test")) - Do $$$AssertNotTrue(##class(%File).DirectoryExists(outDir_"src/src")) - Do $$$AssertNotTrue(##class(%File).DirectoryExists(outDir_"simplemodule")) - Do $$$AssertTrue(##class(%File).Exists(outDir_"src/cls/Test/Test.cls")) - Do $$$AssertTrue(##class(%File).Exists(outDir_"module.xml")) - - Set tSC = ##class(%IPM.Main).Shell("load "_tempDir_".tgz") - Do $$$AssertStatusOK(tSC,"Loaded SimpleModule module successfully from .tgz file.") - - Set tSC = ##class(%IPM.Main).Shell("load "_outDir) - Do $$$AssertStatusOK(tSC,"Loaded SimpleModule module successfully from package directory.") - } Catch e { - Do $$$AssertStatusOK(e.AsStatus(),"An exception occurred.") + set sc = $$$OK + try { + set tTestRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) + + set tModuleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(tTestRoot)_"/_data/simple-module/") + set sc = ##class(%IPM.Main).Shell("load "_tModuleDir) + do $$$AssertStatusOK(sc,"Loaded SimpleModule module successfully.") + + set tempDir = ##class(%Library.File).TempFilename()_"dir" + set sc = ##class(%IPM.Main).Shell("simplemodule package -only -DPath="_tempDir) + do $$$AssertStatusOK(sc,"Packaged SimpleModule successfully.") + + set outFile = ##class(%Library.File).TempFilename() + set outDir = ##class(%Library.File).NormalizeDirectory(##class(%Library.File).TempFilename()_"dir-out") + do ##class(%Library.File).CreateDirectoryChain(outDir) + do $$$AssertEquals($zf(-100,"/STDOUT="""_outFile_"""/STDERR="""_outFile_"""","tar","-xvf",tempDir_".tgz","-C",outDir),0) + + do $$$AssertNotTrue(##class(%File).DirectoryExists(outDir_"src/cls/Test/Test")) + do $$$AssertNotTrue(##class(%File).DirectoryExists(outDir_"src/src")) + do $$$AssertNotTrue(##class(%File).DirectoryExists(outDir_"simplemodule")) + do $$$AssertTrue(##class(%File).Exists(outDir_"src/cls/Test/Test.cls")) + do $$$AssertTrue(##class(%File).Exists(outDir_"module.xml")) + + set sc = ##class(%IPM.Main).Shell("load "_tempDir_".tgz") + do $$$AssertStatusOK(sc,"Loaded SimpleModule module successfully from .tgz file.") + + set sc = ##class(%IPM.Main).Shell("load "_outDir) + do $$$AssertStatusOK(sc,"Loaded SimpleModule module successfully from package directory.") + } catch e { + do $$$AssertStatusOK(e.AsStatus(),"An exception occurred.") + } +} + +Method TestLoadDestinationDirectory() +{ + set sc = $$$OK + try { + set tTestRoot = ##class(%File).NormalizeDirectory($get(^UnitTestRoot)) + set tModuleDir = ##class(%File).NormalizeDirectory(##class(%File).GetDirectory(tTestRoot)_"/_data/simple-module/") + + // Load from directory — files should land in DataDirectory/ipm/// + set sc = ##class(%IPM.Main).Shell("load "_tModuleDir) + do $$$AssertStatusOK(sc,"Loaded SimpleModule from directory.") + set tModule = ##class(%IPM.Storage.Module).NameOpen("simplemodule",,.sc) + do $$$AssertStatusOK(sc,"Opened module after directory load.") + set tExpectedDir = ##class(%Library.File).NormalizeDirectory( + ##class(%SYSTEM.Util).DataDirectory()_"ipm/"_tModule.Name_"/"_tModule.VersionString) + do $$$AssertTrue(##class(%File).DirectoryExists(tExpectedDir),"Module directory exists at standard path after load from directory.") + do $$$AssertTrue(##class(%File).Exists(tExpectedDir_"module.xml"),"module.xml present at standard path after load from directory.") + set sc = ##class(%IPM.Main).Shell("uninstall simplemodule") + do $$$AssertStatusOK(sc,"Uninstalled SimpleModule.") + $$$ThrowOnError(##class(%IPM.Utils.File).RemoveDirectoryTree(tExpectedDir)) + + // Load from tarball — files should also land in DataDirectory/ipm/// + set tempDir = ##class(%Library.File).TempFilename()_"dir" + set sc = ##class(%IPM.Main).Shell("load "_tModuleDir) + do $$$AssertStatusOK(sc,"Loaded SimpleModule from directory to enable packaging.") + set sc = ##class(%IPM.Main).Shell("simplemodule package -only -DPath="_tempDir) + do $$$AssertStatusOK(sc,"Packaged SimpleModule.") + set sc = ##class(%IPM.Main).Shell("uninstall simplemodule") + do $$$AssertStatusOK(sc,"Uninstalled SimpleModule before tarball load.") + set sc = ##class(%IPM.Main).Shell("load "_tempDir_".tgz") + do $$$AssertStatusOK(sc,"Loaded SimpleModule from tarball.") + set tModule = ##class(%IPM.Storage.Module).NameOpen("simplemodule",,.sc) + do $$$AssertStatusOK(sc,"Opened module after tarball load.") + set tExpectedDir = ##class(%Library.File).NormalizeDirectory( + ##class(%SYSTEM.Util).DataDirectory()_"ipm/"_tModule.Name_"/"_tModule.VersionString) + do $$$AssertTrue(##class(%File).DirectoryExists(tExpectedDir),"Module directory exists at standard path after load from tarball.") + set sc = ##class(%IPM.Main).Shell("uninstall simplemodule") + do $$$AssertStatusOK(sc,"Uninstalled SimpleModule.") + + // Load -dev from directory — module Root should point to the original path, not DataDirectory + $$$ThrowOnError(##class(%IPM.Utils.File).RemoveDirectoryTree(tExpectedDir)) + set sc = ##class(%IPM.Main).Shell("load -dev "_tModuleDir) + do $$$AssertStatusOK(sc,"Loaded SimpleModule in dev mode.") + set tModule = ##class(%IPM.Storage.Module).NameOpen("simplemodule",,.sc) + do $$$AssertStatusOK(sc,"Opened module after dev mode load.") + do $$$AssertEquals(tModule.Root, tModuleDir, "Module Root points to original directory in dev mode.") + do $$$AssertNotTrue(##class(%File).DirectoryExists(tExpectedDir),"Module NOT copied to standard path in dev mode.") + set sc = ##class(%IPM.Main).Shell("uninstall simplemodule") + do $$$AssertStatusOK(sc,"Uninstalled SimpleModule.") + } catch e { + do $$$AssertStatusOK(e.AsStatus(),"An exception occurred.") } }