Skip to content

Commit 397c43d

Browse files
committed
fix: add Cargo.lock file validation
1 parent ce33b68 commit 397c43d

2 files changed

Lines changed: 229 additions & 0 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,27 @@ private void addDependencies(
105105
}
106106
}
107107

108+
@Override
109+
public void validateLockFile(Path lockFileDir) {
110+
Path actualLockFileDir = findOutermostCargoTomlDirectory(lockFileDir);
111+
if (!Files.isRegularFile(actualLockFileDir.resolve("Cargo.lock"))) {
112+
throw new IllegalStateException(
113+
"Cargo.lock does not exist or is not supported. Execute 'cargo build' to generate it.");
114+
}
115+
}
116+
117+
private Path findOutermostCargoTomlDirectory(Path startDir) {
118+
Path current = startDir.getParent();
119+
Path outermost = startDir;
120+
while (current != null) {
121+
if (Files.exists(current.resolve("Cargo.toml"))) {
122+
outermost = current;
123+
}
124+
current = current.getParent();
125+
}
126+
return outermost;
127+
}
128+
108129
private CargoMetadata executeCargoMetadata() throws IOException, InterruptedException {
109130
Path workingDir = manifest.getParent();
110131

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* Copyright 2023-2025 Trustify Dependency Analytics Authors
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+
*
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.guacsec.trustifyda.providers;
18+
19+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
22+
23+
import io.github.guacsec.trustifyda.tools.Ecosystem;
24+
import java.io.IOException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.api.io.TempDir;
29+
30+
/**
31+
* Tests for CargoProvider lock file validation functionality. These tests use
32+
* Ecosystem.getProvider() to trigger the validateLockFile method.
33+
*/
34+
public class CargoProviderLockFileValidationTest {
35+
36+
@Test
37+
public void testLockFileValidationWithMissingLockFile(@TempDir Path tempDir) throws IOException {
38+
// Create a valid Cargo.toml without Cargo.lock
39+
Path cargoToml = tempDir.resolve("Cargo.toml");
40+
String content =
41+
"""
42+
[package]
43+
name = "test-project"
44+
version = "1.0.0"
45+
edition = "2021"
46+
47+
[dependencies]
48+
serde = "1.0"
49+
""";
50+
Files.writeString(cargoToml, content);
51+
52+
// Should throw IllegalStateException when calling getProvider (which calls validateLockFile)
53+
IllegalStateException exception =
54+
assertThrows(
55+
IllegalStateException.class,
56+
() -> Ecosystem.getProvider(cargoToml),
57+
"Should throw IllegalStateException for missing Cargo.lock");
58+
59+
assertTrue(exception.getMessage().contains("Cargo.lock does not exist"));
60+
assertTrue(exception.getMessage().contains("cargo build"));
61+
62+
System.out.println("✓ Missing lock file validation test passed!");
63+
}
64+
65+
@Test
66+
public void testLockFileValidationWithValidLockFile(@TempDir Path tempDir) throws IOException {
67+
// Create a valid Cargo.toml
68+
Path cargoToml = tempDir.resolve("Cargo.toml");
69+
String content =
70+
"""
71+
[package]
72+
name = "test-project"
73+
version = "1.0.0"
74+
edition = "2021"
75+
76+
[dependencies]
77+
serde = "1.0"
78+
""";
79+
Files.writeString(cargoToml, content);
80+
81+
// Create a valid Cargo.lock
82+
Path cargoLock = tempDir.resolve("Cargo.lock");
83+
String lockContent =
84+
"""
85+
# This file is automatically @generated by Cargo.
86+
# It is not intended for manual editing.
87+
version = 3
88+
89+
[[package]]
90+
name = "test-project"
91+
version = "1.0.0"
92+
dependencies = [
93+
"serde",
94+
]
95+
96+
[[package]]
97+
name = "serde"
98+
version = "1.0.136"
99+
source = "registry+https://github.com/rust-lang/crates.io-index"
100+
checksum = "13bd41f6daf2677b5378d5aefb23b3b28ad25c27"
101+
""";
102+
Files.writeString(cargoLock, lockContent);
103+
104+
// Should not throw exception for valid lock file
105+
assertDoesNotThrow(
106+
() -> Ecosystem.getProvider(cargoToml), "Should not throw exception for valid Cargo.lock");
107+
108+
System.out.println("✓ Valid lock file validation test passed!");
109+
}
110+
111+
@Test
112+
public void testLockFileValidationWithWorkspaceScenario(@TempDir Path tempDir)
113+
throws IOException {
114+
// Create workspace structure:
115+
// tempDir/
116+
// ├── Cargo.toml (workspace)
117+
// ├── Cargo.lock
118+
// └── member1/
119+
// └── Cargo.toml (member crate)
120+
121+
// Create workspace Cargo.toml
122+
Path workspaceCargoToml = tempDir.resolve("Cargo.toml");
123+
String workspaceContent =
124+
"""
125+
[workspace]
126+
members = ["member1"]
127+
128+
[workspace.package]
129+
version = "1.0.0"
130+
edition = "2021"
131+
""";
132+
Files.writeString(workspaceCargoToml, workspaceContent);
133+
134+
// Create workspace Cargo.lock
135+
Path workspaceCargoLock = tempDir.resolve("Cargo.lock");
136+
String lockContent =
137+
"""
138+
# This file is automatically @generated by Cargo.
139+
# It is not intended for manual editing.
140+
version = 3
141+
142+
[[package]]
143+
name = "member1"
144+
version = "1.0.0"
145+
""";
146+
Files.writeString(workspaceCargoLock, lockContent);
147+
148+
// Create member directory and Cargo.toml
149+
Path memberDir = tempDir.resolve("member1");
150+
Files.createDirectories(memberDir);
151+
Path memberCargoToml = memberDir.resolve("Cargo.toml");
152+
String memberContent =
153+
"""
154+
[package]
155+
name = "member1"
156+
version = { workspace = true }
157+
edition = { workspace = true }
158+
159+
[dependencies]
160+
serde = "1.0"
161+
""";
162+
Files.writeString(memberCargoToml, memberContent);
163+
164+
// Should find workspace lock file even when called with member crate
165+
assertDoesNotThrow(
166+
() -> Ecosystem.getProvider(memberCargoToml),
167+
"Should find workspace Cargo.lock from member crate");
168+
169+
System.out.println("✓ Workspace lock file validation test passed!");
170+
}
171+
172+
@Test
173+
public void testLockFileValidationWithMissingLockInWorkspace(@TempDir Path tempDir)
174+
throws IOException {
175+
// Create workspace structure without Cargo.lock to test error message
176+
Path workspaceCargoToml = tempDir.resolve("Cargo.toml");
177+
String workspaceContent =
178+
"""
179+
[workspace]
180+
members = ["member1"]
181+
""";
182+
Files.writeString(workspaceCargoToml, workspaceContent);
183+
184+
Path memberDir = tempDir.resolve("member1");
185+
Files.createDirectories(memberDir);
186+
Path memberCargoToml = memberDir.resolve("Cargo.toml");
187+
String memberContent =
188+
"""
189+
[package]
190+
name = "member1"
191+
version = "1.0.0"
192+
edition = "2021"
193+
""";
194+
Files.writeString(memberCargoToml, memberContent);
195+
196+
// Should throw exception for missing workspace lock file
197+
IllegalStateException exception =
198+
assertThrows(
199+
IllegalStateException.class,
200+
() -> Ecosystem.getProvider(memberCargoToml),
201+
"Should throw exception for missing workspace Cargo.lock");
202+
203+
assertTrue(exception.getMessage().contains("Cargo.lock does not exist"));
204+
assertTrue(exception.getMessage().contains("cargo build"));
205+
206+
System.out.println("✓ Missing workspace lock file validation test passed!");
207+
}
208+
}

0 commit comments

Comments
 (0)