Skip to content

Commit 4967480

Browse files
committed
test(#6092): Add integration tests for PixiCache functionality
- Introduced PixiCacheIntegrationTest class to validate PixiCache behavior with various environment specifications. - Implemented tests for creating environments from package specifications, TOML files, and lock files. - Added checks for handling custom cache directories and validating TOML and lock file detection. - Enhanced coverage for existing prefix directory handling and environment variable cache directory usage. This commit strengthens the testing framework for PixiCache, ensuring reliable environment management within the Nextflow ecosystem. Signed-off-by: Edmund Miller <[email protected]>
1 parent b7756ee commit 4967480

File tree

2 files changed

+548
-0
lines changed

2 files changed

+548
-0
lines changed
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
/*
2+
* Copyright 2013-2024, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package nextflow.pixi
18+
19+
import java.nio.file.Files
20+
import java.nio.file.Path
21+
import java.nio.file.Paths
22+
23+
import nextflow.util.Duration
24+
import spock.lang.IgnoreIf
25+
import spock.lang.Specification
26+
27+
/**
28+
* Integration tests for PixiCache that require actual Pixi installation
29+
*
30+
* @author Edmund Miller <[email protected]>
31+
*/
32+
class PixiCacheIntegrationTest extends Specification {
33+
34+
/**
35+
* Check if Pixi is installed and available on the system
36+
*/
37+
private static boolean hasPixiInstalled() {
38+
try {
39+
def process = new ProcessBuilder('pixi', '--version').start()
40+
process.waitFor()
41+
return process.exitValue() == 0
42+
} catch (Exception e) {
43+
return false
44+
}
45+
}
46+
47+
@IgnoreIf({ !hasPixiInstalled() })
48+
def 'should create pixi environment from package specification'() {
49+
given:
50+
def tempDir = Files.createTempDirectory('pixi-cache-test')
51+
def config = new PixiConfig([cacheDir: tempDir.toString()], [:])
52+
def cache = new PixiCache(config)
53+
def ENV = 'python>=3.9'
54+
55+
when:
56+
def prefix = cache.pixiPrefixPath(ENV)
57+
58+
then:
59+
prefix != null
60+
prefix.parent == tempDir
61+
prefix.fileName.toString().startsWith('env-')
62+
63+
cleanup:
64+
tempDir?.deleteDir()
65+
}
66+
67+
@IgnoreIf({ !hasPixiInstalled() })
68+
def 'should create pixi environment from TOML file'() {
69+
given:
70+
def tempDir = Files.createTempDirectory('pixi-cache-toml-test')
71+
def config = new PixiConfig([cacheDir: tempDir.toString()], [:])
72+
def cache = new PixiCache(config)
73+
74+
// Create a test TOML file
75+
def tomlFile = tempDir.resolve('test-env.toml')
76+
tomlFile.text = '''
77+
[project]
78+
name = "test-integration"
79+
version = "0.1.0"
80+
description = "Integration test environment"
81+
channels = ["conda-forge"]
82+
platforms = ["linux-64", "osx-64", "osx-arm64"]
83+
84+
[dependencies]
85+
python = ">=3.9"
86+
'''.stripIndent()
87+
88+
when:
89+
def prefix = cache.pixiPrefixPath(tomlFile.toString())
90+
91+
then:
92+
prefix != null
93+
prefix.parent == tempDir
94+
prefix.fileName.toString().startsWith('test-env-')
95+
96+
cleanup:
97+
tempDir?.deleteDir()
98+
}
99+
100+
@IgnoreIf({ !hasPixiInstalled() })
101+
def 'should handle pixi environment creation with custom cache directory'() {
102+
given:
103+
def customCacheDir = Files.createTempDirectory('custom-pixi-cache')
104+
def config = new PixiConfig([
105+
cacheDir: customCacheDir.toString(),
106+
createTimeout: '10min',
107+
createOptions: '--no-lockfile-update'
108+
], [:])
109+
def cache = new PixiCache(config)
110+
111+
when:
112+
def cacheDir = cache.getCacheDir()
113+
114+
then:
115+
cacheDir == customCacheDir
116+
cacheDir.exists()
117+
cache.createTimeout == Duration.of('10min')
118+
cache.createOptions == '--no-lockfile-update'
119+
120+
cleanup:
121+
customCacheDir?.deleteDir()
122+
}
123+
124+
@IgnoreIf({ !hasPixiInstalled() })
125+
def 'should validate TOML file detection'() {
126+
given:
127+
def cache = new PixiCache(new PixiConfig([:], [:]))
128+
129+
expect:
130+
cache.isTomlFilePath('pixi.toml')
131+
cache.isTomlFilePath('pyproject.toml')
132+
cache.isTomlFilePath('/path/to/environment.toml')
133+
!cache.isTomlFilePath('python>=3.9')
134+
!cache.isTomlFilePath('environment.yaml')
135+
!cache.isTomlFilePath('multiline\nstring')
136+
}
137+
138+
@IgnoreIf({ !hasPixiInstalled() })
139+
def 'should validate lock file detection'() {
140+
given:
141+
def cache = new PixiCache(new PixiConfig([:], [:]))
142+
143+
expect:
144+
cache.isLockFilePath('pixi.lock')
145+
cache.isLockFilePath('/path/to/environment.lock')
146+
!cache.isLockFilePath('python>=3.9')
147+
!cache.isLockFilePath('environment.toml')
148+
!cache.isLockFilePath('multiline\nstring')
149+
}
150+
151+
@IgnoreIf({ !hasPixiInstalled() })
152+
def 'should handle existing prefix directory'() {
153+
given:
154+
def tempDir = Files.createTempDirectory('pixi-prefix-test')
155+
def config = new PixiConfig([:], [:])
156+
def cache = new PixiCache(config)
157+
158+
when:
159+
def prefix = cache.pixiPrefixPath(tempDir.toString())
160+
161+
then:
162+
prefix == tempDir
163+
prefix.isDirectory()
164+
165+
cleanup:
166+
tempDir?.deleteDir()
167+
}
168+
169+
@IgnoreIf({ !hasPixiInstalled() })
170+
def 'should handle environment variable cache directory'() {
171+
given:
172+
def envCacheDir = Files.createTempDirectory('env-pixi-cache')
173+
def config = new PixiConfig([:], [NXF_PIXI_CACHEDIR: envCacheDir.toString()])
174+
def cache = Spy(PixiCache, constructorArgs: [config]) {
175+
getEnv() >> [NXF_PIXI_CACHEDIR: envCacheDir.toString()]
176+
}
177+
178+
when:
179+
def cacheDir = cache.getCacheDir()
180+
181+
then:
182+
cacheDir == envCacheDir
183+
cacheDir.exists()
184+
185+
cleanup:
186+
envCacheDir?.deleteDir()
187+
}
188+
189+
@IgnoreIf({ !hasPixiInstalled() })
190+
def 'should create cache from lock file'() {
191+
given:
192+
def tempDir = Files.createTempDirectory('pixi-lock-cache-test')
193+
def config = new PixiConfig([cacheDir: tempDir.toString()], [:])
194+
def cache = new PixiCache(config)
195+
196+
// Create a test lock file (simplified format)
197+
def lockFile = tempDir.resolve('test.lock')
198+
lockFile.text = '''
199+
version: 4
200+
environments:
201+
default:
202+
channels:
203+
- url: https://conda.anaconda.org/conda-forge/
204+
packages:
205+
linux-64:
206+
- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.6-hab00c5b_0_cpython.conda
207+
'''.stripIndent()
208+
209+
when:
210+
def prefix = cache.pixiPrefixPath(lockFile.toString())
211+
212+
then:
213+
prefix != null
214+
prefix.parent == tempDir
215+
prefix.fileName.toString().startsWith('test-')
216+
217+
cleanup:
218+
tempDir?.deleteDir()
219+
}
220+
221+
@IgnoreIf({ !hasPixiInstalled() })
222+
def 'should reject invalid environment specifications'() {
223+
given:
224+
def config = new PixiConfig([:], [:])
225+
def cache = new PixiCache(config)
226+
227+
when:
228+
cache.pixiPrefixPath('invalid\nmultiline\nspec')
229+
230+
then:
231+
thrown(IllegalArgumentException)
232+
}
233+
234+
@IgnoreIf({ !hasPixiInstalled() })
235+
def 'should reject non-existent TOML file'() {
236+
given:
237+
def config = new PixiConfig([:], [:])
238+
def cache = new PixiCache(config)
239+
240+
when:
241+
cache.pixiPrefixPath('/non/existent/file.toml')
242+
243+
then:
244+
thrown(IllegalArgumentException)
245+
}
246+
247+
@IgnoreIf({ !hasPixiInstalled() })
248+
def 'should reject non-existent lock file'() {
249+
given:
250+
def config = new PixiConfig([:], [:])
251+
def cache = new PixiCache(config)
252+
253+
when:
254+
cache.pixiPrefixPath('/non/existent/file.lock')
255+
256+
then:
257+
thrown(IllegalArgumentException)
258+
}
259+
260+
@IgnoreIf({ !hasPixiInstalled() })
261+
def 'should handle complex TOML file with multiple dependencies'() {
262+
given:
263+
def tempDir = Files.createTempDirectory('pixi-complex-toml-test')
264+
def config = new PixiConfig([cacheDir: tempDir.toString()], [:])
265+
def cache = new PixiCache(config)
266+
267+
// Create a complex TOML file
268+
def tomlFile = tempDir.resolve('complex-env.toml')
269+
tomlFile.text = '''
270+
[project]
271+
name = "complex-integration-test"
272+
version = "1.0.0"
273+
description = "Complex integration test environment with multiple dependencies"
274+
channels = ["conda-forge", "bioconda", "pytorch"]
275+
platforms = ["linux-64", "osx-64", "osx-arm64"]
276+
277+
[dependencies]
278+
python = ">=3.9,<3.12"
279+
numpy = ">=1.20"
280+
pandas = ">=1.3"
281+
matplotlib = ">=3.5"
282+
scipy = ">=1.7"
283+
284+
[pypi-dependencies]
285+
requests = ">=2.25"
286+
287+
[tasks]
288+
test = "python -c 'import numpy, pandas, matplotlib, scipy; print(\"All packages imported successfully\")'"
289+
290+
[feature.cuda.dependencies]
291+
pytorch = { version = ">=1.12", channel = "pytorch" }
292+
'''.stripIndent()
293+
294+
when:
295+
def prefix = cache.pixiPrefixPath(tomlFile.toString())
296+
297+
then:
298+
prefix != null
299+
prefix.parent == tempDir
300+
prefix.fileName.toString().startsWith('complex-env-')
301+
302+
cleanup:
303+
tempDir?.deleteDir()
304+
}
305+
}

0 commit comments

Comments
 (0)