@@ -11,123 +11,127 @@ const path = require("path");
1111const { execSync } = require ( "child_process" ) ;
1212const { extractZip } = require ( "./install" ) ;
1313
14- // createTestZip builds a real .zip containing a single file named "supermodel.exe"
15- // using the system zip/tar command. Skips on platforms where neither is available.
16- function createTestZip ( t ) {
17- const src = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "supermodel-test-src-" ) ) ;
18- const binary = path . join ( src , "supermodel.exe" ) ;
19- fs . writeFileSync ( binary , "fake binary" ) ;
14+ const isWindows = process . platform === "win32" ;
15+ const isTarCmd = ( c ) => / t a r ( \. e x e ) ? [ " ' \s ] / . test ( c . split ( " " ) [ 0 ] ) ;
16+ const isPsCmd = ( c ) => c . includes ( "powershell" ) ;
2017
21- const archive = path . join ( os . tmpdir ( ) , `supermodel-test-${ process . pid } .zip` ) ;
18+ // Make a fake extraction succeed by dropping a file into tmpDir so the
19+ // post-extract verification (readdirSync(tmpDir).length > 0) passes.
20+ const fakeExtract = ( tmpDir ) => ( ) => {
21+ fs . writeFileSync ( path . join ( tmpDir , "supermodel.exe" ) , "fake" ) ;
22+ } ;
23+
24+ test ( "extractZip succeeds when the first extractor produces files" , ( ) => {
25+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "smt-ok-" ) ) ;
2226 try {
23- // Use system zip or tar to build the archive.
24- try {
25- execSync ( `zip -j "${ archive } " "${ binary } "` , { stdio : "pipe" } ) ;
26- } catch {
27- execSync ( `tar -cf "${ archive } " --format=zip -C "${ src } " supermodel.exe` , { stdio : "pipe" } ) ;
28- }
29- } catch {
30- fs . rmSync ( src , { recursive : true , force : true } ) ;
31- return null ; // zip tooling not available — caller should skip
27+ const commands = [ ] ;
28+ extractZip ( "/fake/archive.zip" , tmpDir , ( cmd ) => {
29+ commands . push ( cmd ) ;
30+ fakeExtract ( tmpDir ) ( ) ;
31+ } ) ;
32+ assert . equal ( commands . length , 1 , "stops at first successful extractor" ) ;
33+ } finally {
34+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
3235 }
33- fs . rmSync ( src , { recursive : true , force : true } ) ;
34- return archive ;
35- }
36+ } ) ;
3637
37- test ( "extractZip extracts via tar when tar succeeds" , ( ) => {
38- const archive = createTestZip ( ) ;
39- if ( ! archive ) {
40- // Skip gracefully if zip tooling unavailable (e.g. minimal CI image).
41- console . log ( " skipped: zip tooling not available" ) ;
42- return ;
43- }
38+ test ( "extractZip falls back when first extractor throws" , { skip : ! isWindows } , ( ) => {
39+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "smt-throw-" ) ) ;
4440 try {
45- const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "supermodel-test-out-" ) ) ;
46- try {
47- let called = null ;
48- extractZip ( archive , tmpDir , ( cmd ) => {
49- called = cmd ;
50- // Only simulate tar; let the actual extraction happen via real execSync
51- // if this is the tar command.
52- if ( cmd . startsWith ( "tar" ) ) {
53- execSync ( cmd , { stdio : "pipe" } ) ;
54- } else {
55- throw new Error ( "should not reach PowerShell" ) ;
56- }
57- } ) ;
58- assert . ok ( called . startsWith ( "tar" ) , "should have called tar first" ) ;
59- const extracted = fs . readdirSync ( tmpDir ) ;
60- assert . ok ( extracted . length > 0 , "tmpDir should contain extracted files" ) ;
61- } finally {
62- fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
63- }
41+ const commands = [ ] ;
42+ extractZip ( "/fake/archive.zip" , tmpDir , ( cmd ) => {
43+ commands . push ( cmd ) ;
44+ if ( isTarCmd ( cmd ) ) throw new Error ( "tar unavailable" ) ;
45+ fakeExtract ( tmpDir ) ( ) ;
46+ } ) ;
47+ assert . equal ( commands . length , 2 , "tries tar then PowerShell" ) ;
48+ assert . ok ( isTarCmd ( commands [ 0 ] ) , "first attempt is tar" ) ;
49+ assert . ok ( isPsCmd ( commands [ 1 ] ) , "second attempt is PowerShell" ) ;
6450 } finally {
65- fs . unlinkSync ( archive ) ;
51+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
6652 }
6753} ) ;
6854
69- test ( "extractZip falls back to PowerShell when tar fails" , ( ) => {
70- const commands = [ ] ;
71- // Simulate tar failing; PowerShell "succeeds" (no-op).
72- extractZip ( "/fake/archive.zip" , "/fake/tmpdir" , ( cmd ) => {
73- commands . push ( cmd ) ;
74- if ( cmd . startsWith ( "tar" ) ) throw new Error ( "tar not available" ) ;
75- // PowerShell call — just record it, don't execute.
76- } ) ;
77-
78- assert . equal ( commands . length , 2 , "should have attempted tar then PowerShell" ) ;
79- assert . ok ( commands [ 0 ] . startsWith ( "tar" ) , "first call should be tar" ) ;
80- assert . ok ( commands [ 1 ] . includes ( "powershell" ) , "second call should be PowerShell" ) ;
81- assert . ok ( commands [ 1 ] . includes ( "Expand-Archive" ) , "PowerShell command should use Expand-Archive" ) ;
55+ test ( "extractZip falls back when first extractor exits 0 but writes nothing" , { skip : ! isWindows } , ( ) => {
56+ // Reproduces the GNU-tar-in-Git-Bash bug: tar prints "Cannot connect to C:"
57+ // but still exits 0 without extracting anything.
58+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "smt-silent-" ) ) ;
59+ try {
60+ const commands = [ ] ;
61+ extractZip ( "/fake/archive.zip" , tmpDir , ( cmd ) => {
62+ commands . push ( cmd ) ;
63+ if ( isTarCmd ( cmd ) ) return ; // silent no-op "success"
64+ fakeExtract ( tmpDir ) ( ) ;
65+ } ) ;
66+ assert . equal ( commands . length , 2 , "falls back when tar produced no files" ) ;
67+ assert . ok ( isPsCmd ( commands [ 1 ] ) , "fallback is PowerShell Expand-Archive" ) ;
68+ } finally {
69+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
70+ }
8271} ) ;
8372
84- test ( "extractZip PowerShell fallback includes retry loop" , ( ) => {
85- const commands = [ ] ;
86- extractZip ( "/fake/archive.zip" , "/fake/tmpdir" , ( cmd ) => {
87- commands . push ( cmd ) ;
88- if ( cmd . startsWith ( "tar" ) ) throw new Error ( "tar not available" ) ;
89- } ) ;
90-
91- const psCmd = commands . find ( ( c ) => c . includes ( "powershell" ) ) ;
92- assert . ok ( psCmd , "PowerShell command should be present" ) ;
93- assert . ok ( psCmd . includes ( "$RetryCount" ) , "should include retry counter" ) ;
94- assert . ok ( psCmd . includes ( "Start-Sleep" ) , "should include sleep between retries" ) ;
95- assert . ok ( psCmd . includes ( "-lt 10" ) , "should retry up to 10 times" ) ;
73+ test ( "extractZip throws when every extractor fails" , ( ) => {
74+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "smt-fail-" ) ) ;
75+ try {
76+ assert . throws ( ( ) => {
77+ extractZip ( "/fake/archive.zip" , tmpDir , ( ) => {
78+ throw new Error ( "boom" ) ;
79+ } ) ;
80+ } , / b o o m / ) ;
81+ } finally {
82+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
83+ }
9684} ) ;
9785
98- test ( "extractZip uses tar when both succeed — tar wins" , ( ) => {
99- const commands = [ ] ;
100- extractZip ( "/fake/archive.zip" , "/fake/tmpdir" , ( cmd ) => {
101- commands . push ( cmd ) ;
102- // Both would succeed; tar is tried first and doesn't throw.
103- } ) ;
104-
105- assert . equal ( commands . length , 1 , "should only call tar when it succeeds" ) ;
106- assert . ok ( commands [ 0 ] . startsWith ( "tar" ) , "the single call should be tar" ) ;
86+ test ( "extractZip throws when every extractor silently produces nothing" , ( ) => {
87+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "smt-empty-" ) ) ;
88+ try {
89+ assert . throws ( ( ) => {
90+ extractZip ( "/fake/archive.zip" , tmpDir , ( ) => {
91+ // no-op: exit 0, no files — the exact silent-failure mode we must catch
92+ } ) ;
93+ } ) ;
94+ } finally {
95+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
96+ }
10797} ) ;
10898
109- test ( "extractZip passes archive and tmpDir paths into tar command" , ( ) => {
110- const archive = "/tmp/test.zip" ;
111- const tmpDir = "/tmp/extract-dir" ;
112- let tarCmd = null ;
113- extractZip ( archive , tmpDir , ( cmd ) => {
114- tarCmd = cmd ;
115- } ) ;
116-
117- assert . ok ( tarCmd . includes ( archive ) , "tar command should include archive path" ) ;
118- assert . ok ( tarCmd . includes ( tmpDir ) , "tar command should include tmpDir path" ) ;
99+ test ( "extractZip PowerShell fallback uses Expand-Archive with a retry loop" , ( ) => {
100+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "smt-ps-" ) ) ;
101+ try {
102+ const commands = [ ] ;
103+ try {
104+ extractZip ( "/fake/archive.zip" , tmpDir , ( cmd ) => {
105+ commands . push ( cmd ) ;
106+ throw new Error ( "all fail" ) ;
107+ } ) ;
108+ } catch { }
109+ const psCmd = commands . find ( isPsCmd ) ;
110+ assert . ok ( psCmd , "PowerShell command should be attempted" ) ;
111+ assert . ok ( psCmd . includes ( "Expand-Archive" ) , "uses Expand-Archive" ) ;
112+ assert . ok ( psCmd . includes ( "Start-Sleep" ) , "sleeps between retries" ) ;
113+ assert . match ( psCmd , / - l t \s + \d + / , "has a retry counter" ) ;
114+ } finally {
115+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
116+ }
119117} ) ;
120118
121- test ( "extractZip passes archive and tmpDir paths into PowerShell fallback " , ( ) => {
119+ test ( "extractZip passes archive and tmpDir paths into every command " , ( ) => {
122120 const archive = "/tmp/test.zip" ;
123- const tmpDir = "/tmp/extract-dir" ;
124- const commands = [ ] ;
125- extractZip ( archive , tmpDir , ( cmd ) => {
126- commands . push ( cmd ) ;
127- if ( cmd . startsWith ( "tar" ) ) throw new Error ( "tar failed" ) ;
128- } ) ;
129-
130- const psCmd = commands [ 1 ] ;
131- assert . ok ( psCmd . includes ( archive ) , "PowerShell command should include archive path" ) ;
132- assert . ok ( psCmd . includes ( tmpDir ) , "PowerShell command should include tmpDir path" ) ;
121+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "smt-paths-" ) ) ;
122+ try {
123+ const commands = [ ] ;
124+ try {
125+ extractZip ( archive , tmpDir , ( cmd ) => {
126+ commands . push ( cmd ) ;
127+ throw new Error ( "fail all" ) ;
128+ } ) ;
129+ } catch { }
130+ for ( const cmd of commands ) {
131+ assert . ok ( cmd . includes ( archive ) , `archive path present: ${ cmd } ` ) ;
132+ assert . ok ( cmd . includes ( tmpDir ) , `tmpDir path present: ${ cmd } ` ) ;
133+ }
134+ } finally {
135+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
136+ }
133137} ) ;
0 commit comments