diff --git a/src/cls/IPM/Lifecycle/Base.cls b/src/cls/IPM/Lifecycle/Base.cls
index 5b390b7c..f8e35b57 100644
--- a/src/cls/IPM/Lifecycle/Base.cls
+++ b/src/cls/IPM/Lifecycle/Base.cls
@@ -714,7 +714,7 @@ Method InstallPythonRequirements(pRoot As %String = "", ByRef pParams)
Quit tSC
}
-Method ResolvePipCaller(ByRef pParams) As %List
+ClassMethod ResolvePipCaller(ByRef pParams) As %List
{
Set tUseStandalonePip = ##class(%IPM.Repo.UniversalSettings).GetValue("UseStandalonePip")
Set tPipCaller = ##class(%IPM.Repo.UniversalSettings).GetValue("PipCaller")
@@ -734,7 +734,7 @@ Method ResolvePipCaller(ByRef pParams) As %List
Return ..DetectPipCaller(tUseStandalonePip, $Get(pParams("Verbose"), 0))
}
-Method DetectPipCaller(pUseStandalonePip As %Boolean, pVerbose As %Boolean = 0) As %List
+ClassMethod DetectPipCaller(pUseStandalonePip As %Boolean, pVerbose As %Boolean = 0) As %List
{
If pVerbose {
Write !,"Detecting pip caller"
diff --git a/tests/integration_tests/Test/PM/Integration/PythonWheel.cls b/tests/integration_tests/Test/PM/Integration/PythonWheel.cls
new file mode 100644
index 00000000..34a58dcb
--- /dev/null
+++ b/tests/integration_tests/Test/PM/Integration/PythonWheel.cls
@@ -0,0 +1,102 @@
+Class Test.PM.Integration.PythonWheel Extends Test.PM.Integration.Base
+{
+
+/// A reference to Python's sys.path
+Property PythonPath As %SYS.Python;
+
+/// A copy of python list containing the original sys.path
+Property PythonPathCopy As %SYS.Python;
+
+/// Run by RunTest once after all test methods in the test class are run. Can be used to tear down a test environment that was set up by OnBeforeAllTests See example in OnBeforeAllTests.
+Method OnAfterAllTests() As %Status
+{
+ Set copy = ##class(%SYS.Python).Import("copy")
+ Set PythonPath = copy.deepcopy(..PythonPathCopy)
+ Quit $$$OK
+}
+
+/// Run by RunTest once before any test methods in the test class are run. Can be used to set up a
+/// test environment that will be later cleaned up by OnAfterAllTests.
+///
NOTE: OnBeforeAllTests does not currently support skipping tests. Calls to $$$AssertSkipped in
+/// OnBeforeAllTests may result in tests appearing to pass rather than being skipped.
+///
Example: Setup and Cleanup of an environment:
+///
+/// Method OnBeforeAllTests() As %Status +/// { +/// //do setup stuff here +/// set ^inputMessage = "input message" +/// quit $$$OK +/// } +/// Method OnAfterAllTests() As %Status +/// { +/// //do clean up stuff here +/// kill ^inputMessage +/// quit $$$OK +/// }+/// +Method OnBeforeAllTests() As %Status +{ + Set sys = ##class(%SYS.Python).Import("sys") + Set ..PythonPath = sys.path + + Set copy = ##class(%SYS.Python).Import("copy") + Set ..PythonPathCopy = copy.deepcopy(sys.path) + + Quit $$$OK +} + +Method TestPythonWheel() +{ + Set package = "oras" + Set outputDirectory = $$$FileTempDir + Set bundleName = "my-oras-bundle" + Set bundleVersion = "0.1.0" + + // Export all dependencies of ORAS along with ORAS itself + Set deps = ##class(%IPM.Utils.EmbeddedPython).ListDependencies(package, 1, 1) + Do ##class(%IPM.Utils.EmbeddedPython).BundleWheel(deps, outputDirectory, bundleName, bundleVersion) + + // There should be one and only one file - the wheel + set rs = ##class(%File).FileSetFunc(outputDirectory) + Do $$$AssertTrue(rs.%Next()) + Do $$$AssertEquals(rs.Type, "F") + Set wheelPath = rs.Name + Do $$$AssertNotTrue(rs.%Next()) + + // Try install the wheel to another directory + Set pipCaller = ##class(%IPM.Lifecycle.Base).ResolvePipCaller() + // Create a temporary directory for installing the wheel to + Set targetInstallDir = $$$FileTempDir + Set command = pipCaller _ $ListBuild("install", wheelPath, "-t", targetInstallDir) + Set tSC = ##class(%IPM.Utils.Module).RunCommand(, command, .stdout) + Do $$$AssertStatusOK(tSC, "Successfully installed the wheel") + + // Exclude mgr/python from sys.path and include targetInstallDir to check if the installed wheel works + Set mgrPython = ##class(%File).NormalizeDirectory("python", ##class(%File).ManagerDirectory()) + Set mgrPython = $Extract(mgrPython, 1, *-1) // Remove trailing slash + Do ..PythonPath.remove(mgrPython) + Do ..PythonPath.append(targetInstallDir) + Try { + // At this point, oras is probably already in sys.modules cache + // because it was used in OrasTag test (which is alphabetically before this test) + // but if it's not, we import it here so that we can use importlib.reload() with updated sys.path + Set oras = ##class(%SYS.Python).Import("oras") + Set importlib = ##class(%SYS.Python).Import("importlib") + Set oras = importlib.reload(oras) + + Do $$$AssertTrue(oras."__file__" [ targetInstallDir, "oras is imported from the installed wheel") + + // revert oras back to the original one + Do ..PythonPath.remove(targetInstallDir) + Do ..PythonPath.append(mgrPython) + Set oras = importlib.reload(oras) + } Catch ex { + Do $$$AssertFailure("Failed to import oras: "_ex.AsStatus()) + } + + // Clean up temporary directories + Do ##class(%File).RemoveDirectoryTree(outputDirectory) + Do ##class(%File).RemoveDirectoryTree(targetInstallDir) +} + +}