@@ -17,9 +17,11 @@ import {
1717 access ,
1818 chmod ,
1919 constants ,
20+ lstat ,
2021 mkdtemp ,
2122 open ,
2223 readdir ,
24+ readlink ,
2325 rm ,
2426 writeFile
2527} from "node:fs/promises" ;
@@ -35,9 +37,20 @@ import assert from "node:assert";
3537import MakerAppImage from "@reforged/maker-appimage" ;
3638
3739import type { ForgeArch } from "@reforged/maker-appimage" ;
40+ import type { IconSet } from "@reforged/maker-types" ;
41+
42+ const icon : IconSet = { } ;
43+
44+ const icons = Object . freeze ( [
45+ [ "scalable" , "svg" ] ,
46+ [ "1024x1024" , "png" ]
47+ ] as const ) ;
48+
49+ for ( const str of icons )
50+ icon [ str [ 0 ] ] = resolve ( import . meta. dirname , `res/empty_${ str [ 0 ] } .${ str [ 1 ] } ` ) ;
3851
3952/** Maker to test. */
40- const maker = new MakerAppImage ( ) ;
53+ const maker = new MakerAppImage ( { options : { icon } } ) ;
4154await maker . prepareConfig ( process . arch ) ;
4255
4356// Mock app metadata
@@ -88,26 +101,18 @@ const skip = maker.isSupportedOnCurrentPlatform() ?
88101 `One or more binaries are missing: ${ maker . requiredExternalBinaries . join ( ', ' ) } ` :
89102 `Unsupported platform: ${ process . platform } -${ process . arch } ` ;
90103
91- suites . push ( describe ( "MakerAppimage is working correctly" , { skip} , ( ) => {
92- let AResolve : ( arg0 : string ) => any , AReject : ( reason ?: unknown ) => void ;
93- /** Resolved output of successful make, to re-use it in another tests. */
94- const AppImageDir :Promise < string > = new Promise (
95- ( resolve , reject ) => { AResolve = resolve , AReject = reject }
96- ) ;
97-
98- it ( "creates valid AppImage binary" , async ( ) => {
99- maker . make ( {
104+ suites . push ( describe ( "MakerAppimage is working correctly" , { skip} , async ( ) => {
105+ await it ( "successfully creates AppImage binary in predictable path" , async ( ctx ) => {
106+ const AppImageDir = maker . make ( {
100107 packageJSON,
101108 forgeConfig,
102109 appName : packageJSON . productName ,
103110 dir : await mockAppPath ,
104111 makeDir : await mockMkPath ,
105112 targetArch : process . arch as ForgeArch ,
106113 targetPlatform : process . platform
107- } ) . then ( dir => AResolve ( dir [ 0 ] ) , reason => AReject ( reason ) ) ;
108-
114+ } ) . then ( ( [ path ] ) => path ) ;
109115 await assert . doesNotReject ( AppImageDir ) ;
110-
111116 assert . strictEqual (
112117 await AppImageDir ,
113118 resolve (
@@ -117,45 +122,98 @@ suites.push(describe("MakerAppimage is working correctly", {skip}, () => {
117122 `${ packageJSON . productName } -${ packageJSON . version } -${ process . arch } .AppImage`
118123 )
119124 )
120- } ) ;
121-
122- it ( "cleans-up workDir after completion" , async ( ) => {
123- await AppImageDir ;
125+ ctx . test ( "that is an ELF file" , async ( ) => {
126+ const fd = await open ( await AppImageDir ) ;
127+ const buff = new Uint8Array ( 8 ) ;
128+ await fd . read ( buff , 0 , 8 ) ;
129+ assert . strictEqual (
130+ [ ...buff ] . map ( v => v . toString ( 16 ) ) . join ( ' ' ) ,
131+ // ELF magic HEX
132+ "7f 45 4c 46 2 1 1 0"
133+ )
134+ fd . close ( ) ;
135+ } )
136+ ctx . test ( "that is runnable and working fine" , async ctx => {
137+ const exec = promisify ( execFile ) ;
138+ const AppImage = await AppImageDir
139+ // Skip this test for non-exec tmpdir.
140+ if ( await access ( AppImage , constants . X_OK ) . then ( _ => false , _ => true ) ) {
141+ await chmod ( AppImage , 0o755 ) ;
142+ return access ( AppImage , constants . X_OK ) . then (
143+ ( ) => Promise . reject ( "Maker failed to set exec permissions" ) ,
144+ ( ) => ctx . skip ( "Non-executable tmpdir." )
145+ ) ;
146+ }
147+ const cp = exec ( AppImage )
148+ await assert . doesNotReject ( cp ) ;
149+ assert . strictEqual ( ( await cp ) . stdout , "Hello world!\n" )
150+ } )
151+ ctx . test ( "that contains valid icon hierarchy" , async ctx => {
152+ const AppImage = await AppImageDir
153+ // Skip this test for non-exec tmpdir (due to appimage mounting).
154+ if ( await access ( AppImage , constants . X_OK ) . then ( _ => false , _ => true ) ) {
155+ await chmod ( AppImage , 0o755 ) ;
156+ return access ( AppImage , constants . X_OK ) . then (
157+ ( ) => Promise . reject ( "Maker failed to set exec permissions" ) ,
158+ ( ) => ctx . skip ( "Non-executable tmpdir." )
159+ ) ;
160+ }
161+ const cp = execFile ( AppImage , [ "--appimage-mount" ] )
162+ // Wait for mountpoint.
163+ const mount = await new Promise < string > ( ( resolve ) => {
164+ let data = "" ;
165+ cp . stdout ?. once ( "data" , ( chunk :string ) => {
166+ data += String ( chunk ) ;
167+ if ( data . includes ( "\n" ) ) resolve ( data . trimEnd ( ) )
168+ } )
169+ cp . stdout ?. once ( "end" , ( ) => resolve ( data ) )
170+ cp . stdout ?. once ( "close" , ( ) => resolve ( data ) ) ;
171+ } )
172+ // Do FS checks
173+ assert . ok ( mount ) ;
174+ const test = [ ] ;
175+ test . push ( ctx . test ( "with top-level symlinks" , async ( ) => {
176+ const promises : Promise < unknown > [ ] = [ ] ;
177+ for ( const path of [ ".DirIcon" , `${ packageJSON . name } .svg` ] ) {
178+ const file = resolve ( mount , path ) ;
179+ promises . push ( Promise . all ( [ lstat ( file ) , readlink ( file ) ] ) . then ( ( [ stats , link ] ) => {
180+ assert . ok ( stats . isSymbolicLink ( ) , `${ path } is not symlink` ) ;
181+ // Check if default icon is scalable
182+ assert . strictEqual (
183+ link ,
184+ `usr/share/icons/hicolor/scalable/apps/${ packageJSON . name } .svg`
185+ ) ;
186+ } ) ) ;
187+ }
188+ await Promise . all ( promises ) ;
189+ } ) )
190+ test . push ( ctx . test ( "with icons in 'usr/share/icons'" , async ( ) => {
191+ const promises : Promise < unknown > [ ] = [ ] ;
192+ for ( const path of icons )
193+ promises . push ( assert . doesNotReject ( access (
194+ resolve ( mount , "usr/share/icons/hicolor" , path [ 0 ] , "apps" , `${ packageJSON . name } .${ path [ 1 ] } ` ) ,
195+ constants . R_OK
196+ ) ) ) ;
197+ await Promise . all ( promises ) ;
198+ } ) )
199+ try {
200+ assert . ok ( mount ) ;
201+ await Promise . all ( test ) ;
202+ } catch ( err ) {
203+ cp . kill ( ) ;
204+ throw err ;
205+ }
206+ cp . kill ( ) ;
207+ } )
208+ } )
209+ it ( "cleans-up working directory after completion" , async ( ) => {
124210 assert . ok (
125211 // We know workDir is somewhere in tmpdir, but not really its exact path.
126212 // Hence fail for all possible workDir matches.
127213 ! ( await readdir ( tmpdir ( ) ) )
128214 . some ( dir => dir . startsWith ( `.${ packageJSON . productName } -${ packageJSON . version } -${ process . arch } -` ) )
129215 )
130216 } )
131-
132- it ( "outputs AppImage that is an ELF file" , async ( ) => {
133- const fd = await open ( await AppImageDir ) ;
134- const buff = new Uint8Array ( 8 ) ;
135- await fd . read ( buff , 0 , 8 ) ;
136- assert . strictEqual (
137- [ ...buff ] . map ( v => v . toString ( 16 ) ) . join ( ' ' ) ,
138- // ELF magic HEX
139- "7f 45 4c 46 2 1 1 0"
140- )
141- fd . close ( ) ;
142- } )
143-
144- it ( "outputs AppImage that is runnable and working fine" , async ctx => {
145- const exec = promisify ( execFile ) ;
146- const AppImage = await AppImageDir
147- // Skip this test for non-exec tmpdir.
148- if ( await access ( AppImage , constants . X_OK ) . then ( _ => false , _ => true ) ) {
149- await chmod ( AppImage , 0o755 ) ;
150- return access ( AppImage , constants . X_OK ) . then (
151- ( ) => Promise . reject ( "Maker failed to set exec permissions" ) ,
152- ( ) => ctx . skip ( "Non-executable tmpdir." )
153- ) ;
154- }
155- const cp = exec ( AppImage )
156- await assert . doesNotReject ( cp ) ;
157- assert . strictEqual ( ( await cp ) . stdout , "Hello world!\n" )
158- } )
159217} ) ) ;
160218
161219suites . push ( describe ( "MakerAppImage fails for invalid cases" , { skip} , ( ) => {
0 commit comments