1+ // Run with: node --test tests/packaging.test.cjs
2+ // Requires CMake and Bash (override CMAKE_PATH and BASH_PATH on Windows).
3+ const assert = require ( 'node:assert/strict' ) ;
4+ const fs = require ( 'node:fs' ) ;
5+ const os = require ( 'node:os' ) ;
6+ const path = require ( 'node:path' ) ;
7+ const { spawnSync } = require ( 'node:child_process' ) ;
8+ const { test } = require ( 'node:test' ) ;
9+
10+ const source = path . resolve ( __dirname , '..' ) ;
11+ const cmake = process . env . CMAKE_PATH || 'cmake' ;
12+ const bash = process . env . BASH_PATH || 'bash' ;
13+ const posix = ( file ) => file . replaceAll ( '\\' , '/' ) . replace ( / ^ ( [ A - Z a - z ] ) : / , ( _ , drive ) => `/${ drive . toLowerCase ( ) } ` ) ;
14+ const quote = ( value ) => `'${ value . replaceAll ( "'" , "'\\''" ) } '` ;
15+ function fixture ( run ) {
16+ const directory = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'procmon packaging ' ) ) ;
17+ try { return run ( directory ) ; } finally { fs . rmSync ( directory , { recursive : true , force : true } ) ; }
18+ }
19+ function command ( executable , args , directory , env = { } ) {
20+ const result = spawnSync ( executable , args , {
21+ cwd : directory , env : { ...process . env , ...env } , encoding : 'utf8' , timeout : 30000 ,
22+ } ) ;
23+ if ( result . error ) throw result . error ;
24+ return result ;
25+ }
26+ function passed ( result ) { assert . equal ( result . status , 0 , result . stdout + result . stderr ) ; }
27+
28+ function configure ( directory , processor ) {
29+ const script = path . join ( directory , 'configure.cmake' ) ;
30+ fs . writeFileSync ( script , `
31+ include("${ source . replaceAll ( '\\' , '/' ) } /cmake/PackageArchitecture.cmake")
32+ set(PROJECT_VERSION_MAJOR 2)
33+ set(PROJECT_VERSION_MINOR 2)
34+ set(PROJECT_VERSION_PATCH 2)
35+ configure_file("${ source . replaceAll ( '\\' , '/' ) } /dist/DEBIAN.in/control.in" DEBIANcontrol @ONLY)
36+ ` ) ;
37+ return command ( cmake , [ `-DCMAKE_SYSTEM_PROCESSOR=${ processor } ` , '-P' , script ] , directory ) ;
38+ }
39+
40+ function packageDeb ( directory , failure = 0 ) {
41+ const script = path . join ( directory , 'build-package.sh' ) ;
42+ fs . writeFileSync ( script , `
43+ set -e
44+ dpkg-deb() {
45+ if [ "$MOCK_FAILURE" != 0 ]; then return "$MOCK_FAILURE"; fi
46+ for argument in "$@"; do package="$argument"; done
47+ test -f "$package/DEBIAN/control"
48+ test -f "$package/usr/bin/procmon"
49+ touch "$package.deb"
50+ }
51+ # Source in a POSIX-mode shell so dpkg-deb can be mocked without downloading
52+ # or running a Linux package builder. All staging commands execute normally.
53+ set -- ${ quote ( posix ( source ) ) } ${ quote ( posix ( directory ) ) } procmon 2.2.2 0 deb
54+ . ${ quote ( posix ( path . join ( source , 'makePackages.sh' ) ) ) }
55+ ` ) ;
56+ return command ( bash , [ '--posix' , posix ( script ) ] , directory , { MOCK_FAILURE : String ( failure ) } ) ;
57+ }
58+
59+ function stageInputs ( directory ) {
60+ for ( const file of [ 'procmon' , 'procmon.1.gz' , 'changelog.gz' ] ) {
61+ fs . writeFileSync ( path . join ( directory , file ) , 'fixture' ) ;
62+ }
63+ }
64+
65+ for ( const [ processor , architecture ] of [ [ 'x86_64' , 'amd64' ] , [ 'AMD64' , 'amd64' ] , [ 'aarch64' , 'arm64' ] , [ 'arm64' , 'arm64' ] ] ) {
66+ test ( `${ processor } : CMake target controls both Debian metadata and filename` , ( ) => fixture ( ( directory ) => {
67+ passed ( configure ( directory , processor ) ) ;
68+ stageInputs ( directory ) ;
69+ passed ( packageDeb ( directory ) ) ;
70+ const name = `procmon_2.2.2_${ architecture } ` ;
71+ assert . ok ( fs . existsSync ( path . join ( directory , 'deb' , `${ name } .deb` ) ) ) ;
72+ const control = fs . readFileSync ( path . join ( directory , 'deb' , name , 'DEBIAN' , 'control' ) , 'utf8' ) ;
73+ assert . match ( control , new RegExp ( `^Architecture: ${ architecture } $` , 'm' ) ) ;
74+ assert . doesNotMatch ( control , / @ D E B _ A R C H @ / ) ;
75+ assert . equal ( fs . readdirSync ( path . join ( directory , 'deb' ) ) . filter ( ( file ) => file . endsWith ( '.deb' ) ) . length , 1 ) ;
76+ } ) ) ;
77+ }
78+
79+ test ( 'unsupported CMake target is rejected' , ( ) => fixture ( ( directory ) => {
80+ const result = configure ( directory , 'unknown' ) ;
81+ assert . notEqual ( result . status , 0 ) ;
82+ assert . match ( result . stderr , / U n s u p p o r t e d p a c k a g e a r c h i t e c t u r e / ) ;
83+ } ) ) ;
84+
85+ test ( 'missing or unsupported control architecture is rejected' , ( ) => fixture ( ( directory ) => {
86+ stageInputs ( directory ) ;
87+ for ( const control of [ 'Package: procmon\n' , 'Architecture: unknown\n' , 'Architecture: @DEB_ARCH@\n' ] ) {
88+ fs . writeFileSync ( path . join ( directory , 'DEBIANcontrol' ) , control ) ;
89+ const result = packageDeb ( directory ) ;
90+ assert . notEqual ( result . status , 0 ) ;
91+ assert . match ( result . stderr , / U n s u p p o r t e d o r m i s s i n g D e b i a n a r c h i t e c t u r e / ) ;
92+ }
93+ } ) ) ;
94+
95+ test ( 'dpkg-deb failure reaches the build job' , ( ) => fixture ( ( directory ) => {
96+ passed ( configure ( directory , 'aarch64' ) ) ;
97+ stageInputs ( directory ) ;
98+ assert . equal ( packageDeb ( directory , 17 ) . status , 17 ) ;
99+ } ) ) ;
100+
101+ test ( 'missing binary fails staging instead of packaging an incomplete install' , ( ) => fixture ( ( directory ) => {
102+ passed ( configure ( directory , 'aarch64' ) ) ;
103+ stageInputs ( directory ) ;
104+ fs . unlinkSync ( path . join ( directory , 'procmon' ) ) ;
105+ assert . notEqual ( packageDeb ( directory ) . status , 0 ) ;
106+ } ) ) ;
0 commit comments