Python: Add tool to mock env vars in tests.

There is a tool named `EnvironmentVariables` which mocks `System.env`.

We use it to mock `System.env` and `EnvironmentUtil`.

See `EnvironmentVariablesPathMockTest` as an example.

GitOrigin-RevId: ef48bf27d0ee2a1f0e02d62ab8370c01c0ecee52
This commit is contained in:
Ilya.Kazakevich
2025-04-16 21:14:49 +00:00
committed by intellij-monorepo-bot
parent 6cf266d548
commit 0893401e81
3 changed files with 142 additions and 0 deletions
@@ -32,5 +32,44 @@
<orderEntry type="module" module-name="intellij.python.community.testFramework.testEnv" exported="" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.eel.provider" scope="TEST" />
<orderEntry type="module-library" exported="" scope="TEST">
<library name="uk.webcompere.system.stubs.jupiter" type="repository">
<properties maven-id="uk.org.webcompere:system-stubs-jupiter:2.1.8">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-jupiter/2.1.8/system-stubs-jupiter-2.1.8.jar">
<sha256sum>9a24867a51f5d22db67d9052a06bc5dd2e9a3e273bc2ee9814620f2d9f25d0a8</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-core/2.1.8/system-stubs-core-2.1.8.jar">
<sha256sum>9c27322cfc7043c75384ad444007b0880ca18fe7231d69bfa69616bc773cafe1</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.17.4/byte-buddy-1.17.4.jar">
<sha256sum>74768731219a5b2e0cc9d2b1c9e2092e0622242e41b1925a6ba7346e160731bc</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.17.4/byte-buddy-agent-1.17.4.jar">
<sha256sum>e1dae7efc5562c29ad3b625b90e6864208de69bad5632c3f93a547f17622ac51</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-jupiter/2.1.8/system-stubs-jupiter-2.1.8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-core/2.1.8/system-stubs-core-2.1.8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.17.4/byte-buddy-1.17.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.17.4/byte-buddy-agent-1.17.4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-jupiter/2.1.8/system-stubs-jupiter-2.1.8-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-core/2.1.8/system-stubs-core-2.1.8-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.17.4/byte-buddy-1.17.4-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.17.4/byte-buddy-agent-1.17.4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-jupiter/2.1.8/system-stubs-jupiter-2.1.8-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/uk/org/webcompere/system-stubs-core/2.1.8/system-stubs-core-2.1.8-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.17.4/byte-buddy-1.17.4-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.17.4/byte-buddy-agent-1.17.4-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
<orderEntry type="library" scope="TEST" name="io.mockk.jvm" level="project" />
</component>
</module>
@@ -0,0 +1,40 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.junit5Tests.framework
import com.intellij.util.EnvironmentUtil
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables
import java.io.File
import java.nio.file.Path
import kotlin.io.path.pathString
/**
* Extension on top of [EnvironmentVariables] to add [pathsToAdd] to the [PATH].
* Call it before the test. After the test please call [unMockPath].
* See [com.intellij.python.junit5Tests.unit.alsoWin.showCase.EnvironmentVariablesPathMockTest].
*
* It mocks both [System.getenv] and [EnvironmentUtil] used by IJ (latter might cache something).
*/
fun EnvironmentVariables.mockPathAndAdd(vararg pathsToAdd: Path) {
// TODO: Use native calls to `SetEnvironmentVariable` and `setenv(3)` to change env for children processes (they inherit parent envs).
val (pathKey, paths) = variables.entries.find { it.key.isPath }?.toPair()
?: Pair(PATH, "")
val newPathVal = (paths.split(File.pathSeparator) + pathsToAdd.map { it.pathString }).joinToString(File.pathSeparator)
set(pathKey, newPathVal) // Mock System.env
mockkStatic(EnvironmentUtil::class) // Mock EnvironmentUtil
every { EnvironmentUtil.getValue(any()) }.coAnswers {
if ((it.invocation.args[0] as String).isPath) newPathVal else it.invocation.originalCall.invoke() as String
}
}
/**
* See [mockPathAndAdd]
*/
fun EnvironmentVariables.unMockPath() {
unmockkStatic(EnvironmentUtil::class)
}
private const val PATH = "PATH"
private val String.isPath: Boolean get() = uppercase() == PATH
@@ -0,0 +1,63 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.junit5Tests.unit.alsoWin.showCase
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.python.junit5Tests.framework.mockPathAndAdd
import com.intellij.python.junit5Tests.framework.unMockPath
import com.intellij.util.EnvironmentUtil
import org.hamcrest.CoreMatchers
import org.hamcrest.MatcherAssert
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.io.TempDir
import uk.org.webcompere.systemstubs.environment.EnvironmentVariables
import uk.org.webcompere.systemstubs.jupiter.SystemStub
import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension
import java.nio.file.Path
import kotlin.io.path.pathString
/**
* This is how you use [mockPathAndAdd]:
* Extend a case with [SystemStubsExtension], add [EnvironmentVariables], mark it with [SystemStub], call [mockPathAndAdd], [unMockPath]
*/
@ExtendWith(SystemStubsExtension::class)
class EnvironmentVariablesPathMockTest {
@SystemStub
private val environment = EnvironmentVariables()
@TempDir
private lateinit var fakeDir: Path
@BeforeEach
fun setUp() {
PathEnvironmentVariableUtil.findInPath("someData") // Let this class cache something to see how we mock it
environment.mockPathAndAdd(fakeDir)
}
@AfterEach
fun tearDown() {
environment.unMockPath()
}
@Test
fun ensurePathFixedTest() {
Assertions.assertTrue(System.getenv().keys.count() > 2, "Env is too small, seems to be broken")
val pathKey = System.getenv().keys.find { it.lowercase() == "path" }
Assertions.assertNotNull(pathKey, "Env doesn't have path")
MatcherAssert.assertThat("System.env hasn't been changed",
System.getenv(pathKey!!),
CoreMatchers.containsString(fakeDir.pathString))
MatcherAssert.assertThat("PathEnvironmentVariableUtil hasn't been changed",
PathEnvironmentVariableUtil.getPathVariableValue(),
CoreMatchers.containsString(fakeDir.pathString))
for (envVarKey in System.getenv().keys) {
// Make sure we didn't break other vars for EnvironmentUtil
Assertions.assertNotNull(EnvironmentUtil.getValue(envVarKey), "env $envVarKey is broken in EnvironmentUtil")
}
}
}