okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
ff92766
main
1"""The nix and cargo shims through the real kernel: in a jj workspace without 2.git, `nix develop -c` runs a program in a dev shell built from the 3workspace's commit (with its packages and shellHook) and cached, and nix reads 4the flake from that commit instead of copying the directory; cargo goes 5through mbx, with the environment mbx needs to share builds across checkouts.""" 6 7import json 8import os 9import shutil 10import subprocess 11import sys 12import unittest 13from pathlib import Path 14 15ROOT = Path (__file__ ).resolve ().parents [2 ] 16sys .path .insert (0 ,str (ROOT / "priv/python" )) 17import albedo_shims # noqa: E402 18 19from harness import Albedo ,Provider ,exclusive ,python ,text # noqa: E402 20 21 22def script_for (test ): 23def script (request ): 24if request ["messages" ][- 1 ].get ("role" )== "user" : 25return python (test .code ) 26return text ("done" ) 27 28return script 29 30 31def cell_results (app ,session ): 32return [ 33json .loads (part ["value" ]) 34for entry in app .history (session )["items" ] 35if entry ["kind" ]== "tool_result" and entry ["tool" ]["name" ]== "python" 36for part in entry ["content" ] 37if part ["kind" ]== "json" and part ["field" ]== "result" 38 ] 39 40 41FAKE_MBX = """#!/bin/sh 42echo "mbx $* sdk=${SDKROOT-unset} cc=${CC-unset} mode=$MBX_CARGO_SHIM_MODE flags=$HOST_CFLAGS" 43""" 44 45 46class CargoShimTests (unittest .TestCase ): 47def setUp (self ): 48self .provider = Provider (script_for (self )) 49self .addCleanup (self .provider .close ) 50self .app = Albedo (self .provider ) 51self .app .__enter__ () 52self .addCleanup (self .app .__exit__ ,None ,None ,None ) 53 54def test_cargo_runs_through_mbx_without_what_keeps_builds_apart (self ): 55bin = self .app .workspace / "bin" 56bin .mkdir () 57for name ,script in ( 58 ("mbx" ,FAKE_MBX ), 59 ("cc" ,"#!/bin/sh\n" ), 60 ("gcc" ,"#!/bin/sh\n" ), 61 ): 62 (bin / name ).write_text (script ) 63 (bin / name ).chmod (0o755 ) 64# the shell's compiler under another name, as clang is cc in a nix shell 65 (bin / "clang" ).symlink_to ("cc" ) 66session = self .app .session () 67self .code = ( 68f"path = { str ( bin )!r } + ':' + os.environ['PATH']\n" 69"for cc, extra in (('clang', {}), ('clang', {'ALBEDO_NIX_ENV': 'abc'}),\n" 70" ('gcc', {'ALBEDO_NIX_ENV': 'abc'})):\n" 71" env = {'PATH': path, 'SDKROOT': '/nix/store/x-sdk', 'CC': cc, **extra}\n" 72" job = run('cargo', 'build', env=env)\n" 73" await job\n" 74" print(job.tail().strip())\n" 75 ) 76self .app .prompt (session ,"build" ).close () 77self .app .idle (session ) 78lines = cell_results (self .app ,session )[- 1 ]["output" ].splitlines () 79# outside a nix shell a chosen compiler stays 80self .assertEqual (lines [0 ],"mbx build sdk=unset cc=clang mode=1 flags=" ) 81# inside a nix shell the shell's own cc goes to mbx, and the wrapper is keyed 82self .assertEqual ( 83lines [1 ],"mbx build sdk=unset cc=unset mode=1 flags= -DALBEDO_NIX_ENV_abc" 84 ) 85# a shell that picks another compiler keeps it 86self .assertEqual ( 87lines [2 ],"mbx build sdk=unset cc=gcc mode=1 flags= -DALBEDO_NIX_ENV_abc" 88 ) 89 90 91def nixpkgs_lock (): 92"""albedo's own locked nixpkgs: in the store wherever its dev shell is, so 93the test flake needs no network.""" 94lock = json .loads ((ROOT / "flake.lock" ).read_text ()) 95return lock ["nodes" ][lock ["nodes" ]["root" ]["inputs" ]["nixpkgs" ]] 96 97 98FLAKE = """{ 99inputs.nixpkgs.url = "github:%(owner)s/%(repo)s/%(ref)s"; 100outputs = { self, nixpkgs }: { 101devShells = nixpkgs.lib.genAttrs 102[ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ] 103(system: let pkgs = nixpkgs.legacyPackages.${system}; in { 104default = pkgs.mkShell { 105packages = [ (pkgs.writeShellScriptBin "albedo-nix-probe" "echo probe:$ALBEDO_NIX_MARK") ]; 106shellHook = "export ALBEDO_NIX_MARK=from-hook"; 107}; 108}); 109marker = "read-from-the-commit"; 110}; 111} 112""" 113 114 115@exclusive 116@ unittest . skipUnless ( shutil . which ( "nix" ) and shutil . which ( "jj" ), "needs nix and jj" ) 117class NixShellTests (unittest .TestCase ): 118def setUp (self ): 119def prepare (app ): 120# Keep two inherited shim generations to cover nested daemon launches. 121source = albedo_shims .NIX 122inherited = [] 123for name in ("outer-a" ,"outer-b" ): 124directory = app .workspace / name / "shims" / "nix" 125directory .mkdir (parents = True ) 126shim = directory / "nix" 127shim .write_text (source ) 128shim .chmod (0o755 ) 129inherited .append (str (directory )) 130app .env ["PATH" ]= os .pathsep .join ([* inherited ,app .env ["PATH" ]]) 131app .env ["NIX_CONFIG" ]= "experimental-features = nix-command flakes" 132# a first dev shell evaluates nixpkgs; let the cell finish in place 133app .env ["ALBEDO_CELL_BACKGROUND_SECONDS" ]= "900" 134 135self .provider = Provider (script_for (self )) 136self .addCleanup (self .provider .close ) 137self .app = Albedo (self .provider ,prepare = prepare ) 138self .app .__enter__ () 139self .addCleanup (self .app .__exit__ ,None ,None ,None ) 140 141def jj (self ,* args ,cwd ): 142subprocess .run ( 143 ["jj" ,* args ], 144cwd = cwd , 145env = self .app .env , 146check = True , 147capture_output = True , 148timeout = 120 , 149 ) 150 151def test_a_jj_workspace_is_read_from_its_commit_and_its_shell_is_cached (self ): 152node = nixpkgs_lock () 153main = self .app .workspace / "main" 154main .mkdir () 155 (main / "flake.nix" ).write_text (FLAKE % node ["original" ]) 156lock = { 157"nodes" : {"nixpkgs" :node ,"root" : {"inputs" : {"nixpkgs" :"nixpkgs" }}}, 158"root" :"root" , 159"version" :7 , 160 } 161 (main / "flake.lock" ).write_text (json .dumps (lock )) 162 (main / ".gitignore" ).write_text ("/build\n" ) 163self .jj ("git" ,"init" ,cwd = main ) 164self .jj ("commit" ,"-m" ,"flake" ,cwd = main ) 165workspace = self .app .workspace / "ws" 166self .jj ("workspace" ,"add" ,"--name" ,"ws" ,str (workspace ),cwd = main ) 167self .assertFalse ((workspace / ".git" ).exists ()) 168 (workspace / "build" ).mkdir () 169 (workspace / "build" / "output" ).write_bytes (b"\0" * 1_000_000 ) 170session = self .app .session (workspace ) 171self .code = ( 172"for name, argv in (('first', ['nix', 'develop', '-c', 'albedo-nix-probe']),\n" 173" ('again', ['nix', 'develop', '-c', 'albedo-nix-probe']),\n" 174" ('eval', ['nix', 'eval', '--raw', '.#marker'])):\n" 175" job = run(*argv, timeout=900)\n" 176" await job\n" 177" print(name, job.exit_code, json.dumps(job.tail()))\n" 178 ) 179self .app .prompt (session ,"use the dev shell" ).close () 180self .app .idle (session ) 181result = cell_results (self .app ,session )[- 1 ] 182self .assertEqual (result ["status" ],"ok" ,result ) 183runs = { 184name : (int (code ),json .loads (output )) 185for name ,code ,output in ( 186line .split (" " ,2 )for line in result ["output" ].splitlines () 187 ) 188 } 189self .assertEqual (runs ["first" ][0 ],0 ,runs ["first" ]) 190self .assertIn ("albedo: building the dev shell" ,runs ["first" ][1 ]) 191self .assertTrue (runs ["first" ][1 ].endswith ("probe:from-hook\n" )) 192# the cached shell runs the program and says nothing else 193self .assertEqual (runs ["again" ], (0 ,"probe:from-hook\n" )) 194code ,output = runs ["eval" ] 195self .assertEqual (code ,0 ,output ) 196self .assertIn (f"git+file:// { main . resolve () } ?rev=" ,output ) 197self .assertTrue (output .endswith ("read-from-the-commit" )) 198# nothing nix locked went into the main checkout 199self .assertEqual (json .loads ((main / "flake.lock" ).read_text ()),lock ) 200 201 202if __name__ == "__main__" : 203unittest .main ()