Skip to content

Commit e279bff

Browse files
committed
test(provider): Fix failing tests and improve extractArchive error coverage
1 parent 79f70e6 commit e279bff

2 files changed

Lines changed: 319 additions & 0 deletions

File tree

internal/provider/generic_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"os"
99
"path/filepath"
10+
"strings"
1011
"testing"
1112

1213
"github.com/snowdreamtech/unirtm/internal/pkg/env"
@@ -43,6 +44,31 @@ func TestGenericProvider_CalculateExeScore(t *testing.T) {
4344
}
4445
})
4546
}
47+
48+
// Test Windows behavior
49+
oldGOOS := env.RuntimeGOOS
50+
env.RuntimeGOOS = "windows"
51+
52+
winTests := []struct {
53+
name string
54+
toolName string
55+
minScore int
56+
}{
57+
{"tool.exe", "tool", 120}, // 100 + 30 + 20 = 150 (since it checks prefix? Wait, exact match is 100. Then location 30. Then ext 20. So 150. minScore 120 is fine)
58+
{"tool.bat", "tool", 70}, // prefix 50 + location 30 - ext 10 = 70
59+
{"tool.cmd", "tool", 70}, // prefix 50 + location 30 - ext 10 = 70
60+
}
61+
62+
for _, tc := range winTests {
63+
t.Run(tc.name+"_win", func(t *testing.T) {
64+
score := p.calculateExeScore(tc.name, tc.toolName)
65+
if score < tc.minScore {
66+
t.Errorf("expected score >= %d for %s on Windows, got %d", tc.minScore, tc.name, score)
67+
}
68+
})
69+
}
70+
71+
env.RuntimeGOOS = oldGOOS
4672
}
4773

4874
func TestGenericProvider_IsExecutable(t *testing.T) {
@@ -74,6 +100,29 @@ func TestGenericProvider_IsExecutable(t *testing.T) {
74100
t.Errorf("expected %s to be recognized as executable", exePath)
75101
}
76102

103+
// Test Windows behavior for isExecutable
104+
oldGOOS := env.RuntimeGOOS
105+
env.RuntimeGOOS = "windows"
106+
107+
winExePath := filepath.Join(tmpDir, "prog.exe")
108+
f3, _ := os.Create(winExePath)
109+
f3.Close()
110+
info3, _ := os.Stat(winExePath)
111+
if !p.isExecutable(info3) {
112+
t.Errorf("expected %s to be recognized as executable on Windows", winExePath)
113+
}
114+
115+
winBatPath := filepath.Join(tmpDir, "prog.bat")
116+
f4, _ := os.Create(winBatPath)
117+
f4.Close()
118+
info4, _ := os.Stat(winBatPath)
119+
if !p.isExecutable(info4) {
120+
t.Errorf("expected %s to be recognized as executable on Windows", winBatPath)
121+
}
122+
123+
// Restore RuntimeGOOS
124+
env.RuntimeGOOS = oldGOOS
125+
77126
txtPath := filepath.Join(tmpDir, "test.txt")
78127
f2, _ := os.Create(txtPath)
79128
f2.Close()
@@ -188,6 +237,23 @@ func TestGenericProvider_CopyFile(t *testing.T) {
188237
}
189238
}
190239

240+
func TestGenericProvider_GenerateWindowsShim(t *testing.T) {
241+
p := NewGenericProvider()
242+
243+
shim := p.generateWindowsShim("bin.exe", "1.0.0")
244+
expectedLines := []string{
245+
"@echo off",
246+
"REM UniRTM shim for bin.exe (version 1.0.0)",
247+
"\"bin.exe\" %*",
248+
"",
249+
}
250+
expected := strings.Join(expectedLines, "\n")
251+
252+
if shim != expected {
253+
t.Errorf("expected %q, got %q", expected, shim)
254+
}
255+
}
256+
191257
func TestGenericProvider_ValidateInstallDir(t *testing.T) {
192258
p := NewGenericProvider()
193259
tmpDir := t.TempDir()
@@ -248,6 +314,30 @@ func TestGenericProvider_FlattenDirectory(t *testing.T) {
248314
if _, err := os.Stat(subDir); !os.IsNotExist(err) {
249315
t.Error("subdir was not removed")
250316
}
317+
318+
// Test double-nested case (flattenDirectory recursion)
319+
ctx := context.Background()
320+
doubleNestedPath := filepath.Join(tmpDir, "double_nested")
321+
os.MkdirAll(filepath.Join(doubleNestedPath, "tool-v1", "tool-v2"), 0755)
322+
os.WriteFile(filepath.Join(doubleNestedPath, "tool-v1", "tool-v2", "file.txt"), []byte("data"), 0644)
323+
err = p.flattenDirectory(ctx, doubleNestedPath)
324+
if err != nil {
325+
t.Fatalf("unexpected error: %v", err)
326+
}
327+
if _, err := os.Stat(filepath.Join(doubleNestedPath, "file.txt")); os.IsNotExist(err) {
328+
t.Errorf("expected double nested file.txt to be lifted")
329+
}
330+
331+
// Test flattenDirectory fails to remove (unremovable dir, e.g. permission issue or dummy mock? we can skip, or just standard dir skip)
332+
standardDirs := []string{"bin", "lib", "include", "share", "etc", "man"}
333+
for _, sd := range standardDirs {
334+
stdPath := filepath.Join(tmpDir, "std_"+sd)
335+
os.MkdirAll(filepath.Join(stdPath, sd), 0755)
336+
p.flattenDirectory(ctx, stdPath)
337+
if _, err := os.Stat(filepath.Join(stdPath, sd)); os.IsNotExist(err) {
338+
t.Errorf("expected standard dir %s to not be flattened", sd)
339+
}
340+
}
251341
}
252342

253343
func TestGenericProvider_RelativizeAllSymlinks(t *testing.T) {
@@ -280,6 +370,26 @@ func TestGenericProvider_RelativizeAllSymlinks(t *testing.T) {
280370
if linkTarget != "target.txt" {
281371
t.Errorf("expected target.txt, got %s", linkTarget)
282372
}
373+
374+
// Test already relative link skip
375+
relLinkPath := filepath.Join(tmpDir, "rel_link")
376+
os.Symlink("target.txt", relLinkPath)
377+
p.relativizeAllSymlinks(tmpDir)
378+
relTarget, _ := os.Readlink(relLinkPath)
379+
if relTarget != "target.txt" {
380+
t.Errorf("expected target.txt, got %s", relTarget)
381+
}
382+
383+
// Test absolute link outside directory
384+
outsideTarget := filepath.Join(t.TempDir(), "outside.txt")
385+
os.WriteFile(outsideTarget, []byte("outside"), 0644)
386+
outLinkPath := filepath.Join(tmpDir, "out_link")
387+
os.Symlink(outsideTarget, outLinkPath)
388+
p.relativizeAllSymlinks(tmpDir)
389+
outTarget, _ := os.Readlink(outLinkPath)
390+
if outTarget != outsideTarget {
391+
t.Errorf("expected %s, got %s", outsideTarget, outTarget)
392+
}
283393
}
284394

285395
func TestGenericProvider_FindExecutables(t *testing.T) {
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Copyright (c) 2026 SnowdreamTech. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
3+
4+
package provider
5+
6+
import (
7+
"archive/tar"
8+
"archive/zip"
9+
"compress/gzip"
10+
"context"
11+
"os"
12+
"path/filepath"
13+
"strings"
14+
"testing"
15+
)
16+
17+
func TestGenericProvider_InstallArchiveCoverage(t *testing.T) {
18+
p := NewGenericProvider()
19+
ctx := context.Background()
20+
21+
tmpDir := t.TempDir()
22+
installPath := filepath.Join(tmpDir, "install_dir")
23+
artifactPath := filepath.Join(tmpDir, "test.zip")
24+
25+
// Create a valid zip file
26+
f, err := os.Create(artifactPath)
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
zw := zip.NewWriter(f)
31+
32+
// Add a directory
33+
dirHeader := &zip.FileHeader{
34+
Name: "subdir/",
35+
Method: zip.Store,
36+
}
37+
dirHeader.SetMode(0755 | os.ModeDir)
38+
zw.CreateHeader(dirHeader)
39+
40+
// Add an executable file inside
41+
header := &zip.FileHeader{
42+
Name: "subdir/mytool",
43+
Method: zip.Store,
44+
}
45+
header.SetMode(0755) // Executable mode
46+
47+
w, err := zw.CreateHeader(header)
48+
if err != nil {
49+
t.Fatal(err)
50+
}
51+
w.Write([]byte("binary data"))
52+
53+
// Add a Zip Slip file
54+
slipHeader := &zip.FileHeader{
55+
Name: "../slip.txt",
56+
Method: zip.Store,
57+
}
58+
wSlip, _ := zw.CreateHeader(slipHeader)
59+
wSlip.Write([]byte("slip"))
60+
61+
zw.Close()
62+
f.Close()
63+
64+
err = p.Install(ctx, "mytool", installPath, artifactPath, "1.0.0")
65+
if err != nil {
66+
t.Errorf("Unexpected error for archive install: %v", err)
67+
}
68+
69+
// Check if bin/mytool was created (since flattenDirectory will lift subdir/mytool)
70+
dstPath := filepath.Join(installPath, "bin", "mytool")
71+
if _, err := os.Stat(dstPath); os.IsNotExist(err) {
72+
t.Errorf("Expected executable to be linked to %s", dstPath)
73+
}
74+
}
75+
76+
func TestGenericProvider_ExtractArtifactMoreCoverage(t *testing.T) {
77+
p := NewGenericProvider()
78+
ctx := context.Background()
79+
80+
tmpDir := t.TempDir()
81+
82+
// Test unsupported archive type
83+
unsupportedPath := filepath.Join(tmpDir, "test.unsupported")
84+
os.WriteFile(unsupportedPath, []byte("dummy"), 0644)
85+
err := p.extractArtifact(ctx, unsupportedPath, tmpDir)
86+
if err == nil {
87+
t.Errorf("Expected error for unsupported archive type")
88+
}
89+
90+
// Test extracting a single compressed file (.gz)
91+
gzPath := filepath.Join(tmpDir, "single.gz")
92+
fGz, _ := os.Create(gzPath)
93+
gw := gzip.NewWriter(fGz)
94+
gw.Write([]byte("single decompressed data"))
95+
gw.Close()
96+
fGz.Close()
97+
98+
err = p.extractArtifact(ctx, gzPath, tmpDir)
99+
if err != nil {
100+
t.Errorf("Unexpected error for .gz extraction: %v", err)
101+
}
102+
extractedSinglePath := filepath.Join(tmpDir, "single")
103+
if _, err := os.Stat(extractedSinglePath); os.IsNotExist(err) {
104+
t.Errorf("Expected single file to be extracted to %s", extractedSinglePath)
105+
}
106+
107+
// Test extracting a tar file
108+
tarPath := filepath.Join(tmpDir, "test.tar")
109+
fTar, _ := os.Create(tarPath)
110+
tw := tar.NewWriter(fTar)
111+
tw.WriteHeader(&tar.Header{
112+
Name: "tardir/",
113+
Typeflag: tar.TypeDir,
114+
Mode: 0755,
115+
})
116+
tw.WriteHeader(&tar.Header{
117+
Name: "tarfile.txt",
118+
Typeflag: tar.TypeReg,
119+
Mode: 0644,
120+
Size: int64(len("tar data")),
121+
})
122+
tw.Write([]byte("tar data"))
123+
tw.WriteHeader(&tar.Header{
124+
Name: "tarsymlink",
125+
Typeflag: tar.TypeSymlink,
126+
Linkname: "tarfile.txt",
127+
})
128+
tw.WriteHeader(&tar.Header{
129+
Name: "../tarslip.txt",
130+
Typeflag: tar.TypeReg,
131+
Mode: 0644,
132+
Size: 4,
133+
})
134+
tw.Write([]byte("slip"))
135+
tw.Close()
136+
fTar.Close()
137+
138+
err = p.extractArtifact(ctx, tarPath, tmpDir)
139+
if err != nil {
140+
t.Errorf("Unexpected error for .tar extraction: %v", err)
141+
}
142+
143+
// Test extracting a tar.gz file
144+
targzPath := filepath.Join(tmpDir, "test.tar.gz")
145+
fTargz, _ := os.Create(targzPath)
146+
gw2 := gzip.NewWriter(fTargz)
147+
tw2 := tar.NewWriter(gw2)
148+
tw2.WriteHeader(&tar.Header{
149+
Name: "targzdir/",
150+
Typeflag: tar.TypeDir,
151+
Mode: 0755,
152+
})
153+
tw2.WriteHeader(&tar.Header{
154+
Name: "targzfile.txt",
155+
Typeflag: tar.TypeReg,
156+
Mode: 0644,
157+
Size: int64(len("targz data")),
158+
})
159+
tw2.Write([]byte("targz data"))
160+
tw2.WriteHeader(&tar.Header{
161+
Name: "targzsymlink",
162+
Typeflag: tar.TypeSymlink,
163+
Linkname: "targzfile.txt",
164+
})
165+
tw2.Close()
166+
gw2.Close()
167+
fTargz.Close()
168+
169+
err = p.extractArtifact(ctx, targzPath, tmpDir)
170+
if err != nil {
171+
t.Errorf("Unexpected error for .tar.gz extraction: %v", err)
172+
}
173+
174+
// Test extracting an unsupported archive format
175+
unsupportedPath2 := filepath.Join(tmpDir, "test.unsupported2")
176+
os.WriteFile(unsupportedPath2, []byte("data"), 0644)
177+
err = p.extractArtifact(ctx, unsupportedPath2, tmpDir)
178+
if err == nil || !strings.Contains(err.Error(), "unsupported archive type") {
179+
t.Errorf("Expected unsupported archive type error, got %v", err)
180+
}
181+
182+
// Test extracting into a read-only directory to trigger os.MkdirAll / os.OpenFile errors
183+
readOnlyDir := filepath.Join(tmpDir, "readonly")
184+
os.MkdirAll(readOnlyDir, 0555) // Read and execute only, no write
185+
186+
// Create a dummy zip file
187+
zipPath := filepath.Join(tmpDir, "test_readonly.zip")
188+
fZip, _ := os.Create(zipPath)
189+
zw := zip.NewWriter(fZip)
190+
fWriter, _ := zw.Create("file.txt")
191+
fWriter.Write([]byte("data"))
192+
zw.Close()
193+
fZip.Close()
194+
195+
// zip extraction error
196+
err = p.extractArtifact(ctx, zipPath, readOnlyDir)
197+
if err == nil {
198+
t.Error("Expected error when extracting zip to read-only directory, got nil")
199+
}
200+
201+
// tar extraction error
202+
err = p.extractArtifact(ctx, tarPath, readOnlyDir)
203+
if err == nil {
204+
t.Error("Expected error when extracting tar to read-only directory, got nil")
205+
}
206+
207+
// Reset permissions so tmpDir cleanup doesn't fail
208+
os.Chmod(readOnlyDir, 0755)
209+
}

0 commit comments

Comments
 (0)