Преглед изворни кода

feat(manifest): add test script support to package lifecycle

Add test script execution capability to the USM package management system.
The test script runs after build but before installation to validate build
artifacts through unit and integration tests.

Changes include:
- Added "test" field to Executables class and manifest schema
- Implemented test execution in CLI manifest command
- Added test phase to transaction pipeline
- Updated documentation with test script specifications
- Modified MANIFEST.usm to include new documentation resources

The test script receives build directory as argument and has access to
standard USM environment variables for package configuration.
clanker пре 1 месец
родитељ
комит
74b43d7e90

+ 3 - 3
MANIFEST.usm

@@ -12,12 +12,12 @@
     "typelib:usm-1.0.typelib": "as-expected",
     "gir:usm-1.0.gir": "as-expected",
     "pc:usm.pc": "as-expected",
-    "res:slopdocs/structure.usm.manifest.packaging.md": "source:slopdocs/structure.usm.manifest.packaging.md",
     "res:slopdocs/structure.usm.manifest.md": "source:slopdocs/structure.usm.manifest.md",
+    "res:slopdocs/structure.usm.manifest.packaging.md": "source:slopdocs/structure.usm.manifest.packaging.md",
+    "res:slopdocs/utility.usm.manifest.autoprovides.md": "source:slopdocs/utility.usm.manifest.autoprovides.md",
     "res:slopdocs/structure.usm.manifest.resource-types.md": "source:slopdocs/structure.usm.manifest.resource-types.md",
     "res:slopdocs/structure.usm.manifest.executable-scripts.md": "source:slopdocs/structure.usm.manifest.executable-scripts.md",
-    "res:slopdocs/structure.usm.manifest.dependency-management.md": "source:slopdocs/structure.usm.manifest.dependency-management.md",
-    "res:slopdocs/utility.usm.manifest.autoprovides.md": "source:slopdocs/utility.usm.manifest.autoprovides.md"
+    "res:slopdocs/structure.usm.manifest.dependency-management.md": "source:slopdocs/structure.usm.manifest.dependency-management.md"
   },
   "depends": {
     "runtime": [

+ 2 - 1
README.md

@@ -30,7 +30,8 @@
     "install": "usp-exec/install",
     "remove": "usp-exec/remove",
     "build": "usp-exec/build",
-    "rebuild": "usp-exec/rebuild"
+    "rebuild": "usp-exec/rebuild",
+    "test": "usp-exec/test"
   }
 }
 ```

+ 26 - 0
slopdocs/structure.usm.manifest.executable-scripts.md

@@ -130,6 +130,32 @@ echo "Cleaning up..."
 rm ${ARCHIVE_NAME}
 ```
 
+### test
+Optional script that runs tests after build but before installation.
+
+**Arguments**: `[build-directory]`
+- `build-directory`: Path containing build output
+
+**Environment Variables**:
+- `USM_DESTDIR`, `USM_PREFIX`, `USM_BINDIR`, etc. are set
+
+**Use Case**: Unit tests, integration tests, validation of build artifacts
+
+**Example**:
+```bash
+#!/bin/bash
+set -e
+
+build_dir=$1
+
+cd ${build_dir}
+# Run unit tests
+./test-suite
+# Run integration tests
+./integration-tests
+echo "All tests passed"
+```
+
 ## Script Requirements
 
 ### Permissions

+ 3 - 1
slopdocs/structure.usm.manifest.md

@@ -83,12 +83,14 @@ Executable scripts for different package lifecycle phases.
 - `remove` (string, optional): Removal script path
 - `postInstall` (string, optional): Post-install script path
 - `acquire` (string, optional): Source acquisition script path
+- `test` (string, optional): Test script path
 
 ```json
 "execs": {
   "build": "scripts/build.sh",
   "install": "scripts/install.sh",
-  "acquire": "scripts/acquire.sh"
+  "acquire": "scripts/acquire.sh",
+  "test": "scripts/test.sh"
 }
 ```
 

+ 67 - 1
src/cli/Manifest.vala

@@ -82,13 +82,16 @@ public static int manifest_main(string[] args) {
         return autoprovides();
     }
 
+    if(verb == "test") {
+        return test();
+    }
 
     manifest_usage();
     return 255;
 }
 
 private void manifest_usage() {
-    printerr("USAGE:\n\tusm manifest build <build path>\n\tusm manifest install <build path>\n\tusm manifest remove\nusm manifest acquire\nusm manifest autoprovides [--replace] [build path]\n");
+    printerr("USAGE:\n\tusm manifest build <build path>\n\tusm manifest install <build path>\n\tusm manifest remove\nusm manifest acquire\nusm manifest autoprovides [--replace] [build path]\nusm manifest test [build path]\n");
 }
 
 
@@ -592,4 +595,67 @@ private static string get_relative_path_for_type(string path, string type) {
     
     // Default case - return the path as-is
     return clean_path;
+}
+
+private int test() {
+    if(build_path == null) {
+        var dir = File.new_build_filename("/tmp", Uuid.string_random());
+        try {
+            dir.make_directory();
+        }
+        catch(Error e) {
+            printerr(@"Could not create temporary build directory, try specifying one instead: $(e.message)\n");
+            return 255;
+        }
+        build_path = dir.get_path();
+    }
+
+    var missing_management_deps = manifest.dependencies.manage.where(d => !d.is_satisfied());
+    var missing_build_deps = manifest.dependencies.build.where(d => !d.is_satisfied());
+
+    var sane = true;
+    if(missing_management_deps.any()) {
+        sane = false;
+        foreach (var item in missing_management_deps) {
+            printerr(@"Missing management dependency \"$item\".\n");
+        }
+    }
+    if(missing_build_deps.any()) {
+        sane = false;
+        foreach (var item in missing_build_deps) {
+            printerr(@"Missing build dependency \"$item\".\n");
+        }
+    }
+
+    if(!sane) {
+        printerr("Could not test manifest, missing dependencies\n");
+        return 252;
+    }
+
+    try {
+        // First build the package
+        var build_proc = manifest.run_build(build_path, paths, SubprocessFlags.INHERIT_FDS, frac => printerr(@"Building '$(manifest.name)': $((int)(frac*100))%\r"));
+        build_proc.wait_check();
+        printerr("\n");
+    }
+    catch(Error e) {
+        printerr(@"\nError running build exec: $(e.message)\n");
+        return 251;
+    }
+
+    if(manifest.executables.test == null) {
+        printerr(@"Manifest does not reference a test script\n");
+        return 248;
+    }
+
+    try {
+        var test_proc = manifest.run_test(build_path, SubprocessFlags.INHERIT_FDS);
+        test_proc.wait_check();
+    }
+    catch(Error e) {
+        printerr(@"Error running test exec: $(e.message)\n");
+        return 251;
+    }
+
+    return 0;
 }

+ 4 - 0
src/lib/Exectuables.vala

@@ -10,6 +10,7 @@ namespace Usm {
         public string? rebuild { get; set; }
         public string? post_install { get; set; }
         public string? acquire { get; set; }
+        public string? test { get; set; }
 
         public static PropertyMapper<Executables> get_mapper() {
             return PropertyMapper.build_for<Executables>(cfg => {
@@ -29,6 +30,9 @@ namespace Usm {
                 cfg.map<string?>("remove", o => o.remove, (o, v) => o.remove = v)
                     .undefined_when(o => o.remove == null)
                     .when_undefined(o => o.remove = null);
+                cfg.map<string?>("test", o => o.test, (o, v) => o.test = v)
+                    .undefined_when(o => o.test == null)
+                    .when_undefined(o => o.test = null);
                 cfg.set_constructor(() => new Executables());
             });
         }

+ 9 - 0
src/lib/Manifest.vala

@@ -272,6 +272,15 @@ namespace Usm {
             return proc;
         }
 
+        public Subprocess? run_test(string build_path, SubprocessFlags flags) throws Error {
+            if(executables.test == null) {
+                return null;
+            }
+            var path = Path.build_filename(Environment.get_current_dir(), executables.test);
+            var proc = new Subprocess.newv(new string[] { path, build_path }, flags);
+            return proc;
+        }
+
         public delegate void ResourceProgressCallback(ResourceRef resource, uint current_resource, uint total_resources, float resource_frac);
         public void install_resources(string source_path, string build_path, string? install_path, Paths paths, ResourceProgressCallback callback, bool dry_run = false) throws Error {
             // Install each resource speficied by the manifest

+ 39 - 18
src/lib/Transaction.vala

@@ -35,6 +35,9 @@ namespace Usm {
                 // 3. Build packages
                 do_for(lot, build_package, TransactionTask.BUILDING);
     
+                // 4. Test packages
+                do_for(lot, test_package, TransactionTask.TESTING);
+    
                 // 5. Install packages
                 do_for(lot, install_package, TransactionTask.INSTALLING);
             }
@@ -65,7 +68,7 @@ namespace Usm {
         }
 
         public void strategise() throws TransactionError {
-            task_count = (to_install.count() * 4) + (to_remove.count() * 3) + 1;
+            task_count = (to_install.count() * 5) + (to_remove.count() * 3) + 1;
             uint strategise_worst_case_task_count = (to_remove.count() * to_remove.count()) + (to_install.count() * to_install.count());
             uint strategise_current_task = 0;
 
@@ -212,6 +215,21 @@ namespace Usm {
             build_proc.wait_check();
         }
 
+        private void test_package(CachedPackage package) throws Error {
+            var source_dir = package.get_source_directory();
+            var build_dir = package.get_build_directory();
+
+            // "cd" into the source directory and read the manifest
+            Environment.set_current_dir(source_dir);
+            var manifest = new Usm.Manifest.from_file("MANIFEST.usm");
+
+            // Run test process if present
+            var test_proc = manifest.run_test(build_dir, SubprocessFlags.STDOUT_SILENCE);
+            if(test_proc != null) {
+                test_proc.wait_check();
+            }
+        }
+
         private void remove_package(CachedPackage package) throws Error {
             // Get source and build directories
             var source_dir = package.get_source_directory();
@@ -274,29 +292,32 @@ namespace Usm {
         STRATEGISING,
         UNPACKING,
         BUILDING,
+        TESTING,
         REMOVING,
         INSTALLING,
         CLEANING_UP;
 
-        public string get_verb() {
-            switch (this) {
-                case STRATEGISING:
-                    return "preparing a strategy for";
-                case UNPACKING:
-                    return "unpacking";
-                case BUILDING:
-                    return "building";
-                case REMOVING:
-                    return "removing";
-                case INSTALLING:
-                    return "installing";
-                case CLEANING_UP:
-                    return "cleaning up";
-                default:
-                    assert_not_reached();
-            }
+    public string get_verb() {
+        switch (this) {
+            case STRATEGISING:
+                return "preparing a strategy for";
+            case UNPACKING:
+                return "unpacking";
+            case BUILDING:
+                return "building";
+            case TESTING:
+                return "testing";
+            case REMOVING:
+                return "removing";
+            case INSTALLING:
+                return "installing";
+            case CLEANING_UP:
+                return "cleaning up";
+            default:
+                assert_not_reached();
         }
     }
+    }
 
     public errordomain TransactionError {