···1414 close_in ic;
1515 s
16161717-let read_json path =
1818- let s = read_file path in
1919- match Jsont_bytesrw.decode_string Jsont.json_value s with
2020- | Ok j -> j
2121- | Error e -> Fmt.failwith "failed to parse %s: %s" path e
1717+(* Simple JSON string field extraction for meta.json *)
1818+let meta_field key =
1919+ let raw = read_file "meta.json" in
2020+ let pat = Fmt.str {|"%s": "|} key in
2121+ match Astring.String.find_sub ~sub:pat raw with
2222+ | None -> Fmt.failwith "meta.json: missing field %s" key
2323+ | Some i ->
2424+ let start = i + String.length pat in
2525+ let stop =
2626+ match String.index_from_opt raw start '"' with
2727+ | Some j -> j
2828+ | None -> Fmt.failwith "meta.json: unterminated string for %s" key
2929+ in
3030+ String.sub raw start (stop - start)
22312332(* {1 Index parsing} *)
24332534let test_parse_index () =
2626- let json = read_json "index.json" in
2727- match Index.of_yojson json with
2828- | Error e -> fail e
2929- | Ok index ->
3535+ let raw = read_file "index.json" in
3636+ match Manifest.of_string raw with
3737+ | Error (`Msg e) -> fail e
3838+ | Ok (`OCI_index index) ->
3039 let manifests = Index.manifests index in
3140 check bool "has manifests" true (List.length manifests > 0);
3232- (* alpine:3.19.4 is multi-arch — at least amd64, arm64, arm/v6, etc. *)
3341 check bool "multiple manifests" true (List.length manifests >= 7);
3434- (* Check that platform info is present on manifests *)
3542 let has_amd64 =
3643 List.exists
3744 (fun d ->
···4350 manifests
4451 in
4552 check bool "has linux/amd64" true has_amd64
5353+ | Ok _ -> fail "expected OCI index"
5454+5555+let test_index_annotations () =
5656+ let raw = read_file "index.json" in
5757+ match Manifest.of_string raw with
5858+ | Error (`Msg e) -> fail e
5959+ | Ok (`OCI_index index) ->
6060+ let manifests = Index.manifests index in
6161+ let first = List.hd manifests in
6262+ let annots = Descriptor.annotations first in
6363+ check bool "descriptor has annotations" true (List.length annots > 0);
6464+ let has_version =
6565+ List.exists (fun (k, _) -> k = Annotation.Version) annots
6666+ in
6767+ check bool "has version annotation" true has_version
6868+ | Ok _ -> fail "expected OCI index"
46694770let test_index_digest () =
4848- let meta_json = read_file "meta.json" in
4949- let meta =
5050- match Jsont_bytesrw.decode_string Jsont.json_value meta_json with
5151- | Ok j -> j
5252- | Error e -> Fmt.failwith "meta.json: %s" e
5353- in
5454- (* Verify the index digest matches what the registry reported *)
5555- let expected_digest =
5656- match meta with
5757- | Jsont.Object (members, _) -> (
5858- match List.find_opt (fun (k, _) -> k = "index_digest") members with
5959- | Some (_, Jsont.String (s, _)) -> s
6060- | _ ->
6161- fail "missing index_digest in meta.json";
6262- "")
6363- | _ ->
6464- fail "meta.json is not an object";
6565- ""
6666- in
6767- (* Compute digest from the raw index JSON *)
6868- let raw = read_file "index.json" in
6969- let computed = Digest.hash SHA256 raw in
7070- (* The registry may have formatted differently, so we compare parse results
7171- rather than raw bytes. Just verify we can parse the expected digest. *)
7272- match Digest.of_string expected_digest with
7171+ let expected = meta_field "index_digest" in
7272+ match Digest.of_string expected with
7373 | Ok d ->
7474 check string "algorithm" "sha256"
7575 (Digest.string_of_algorithm (Digest.algorithm d));
7676 check bool "non-empty hash" true
7777- (String.length (Digest.encoded_hash d) > 0);
7878- check bool "computed non-empty" true
7979- (String.length (Digest.encoded_hash computed) > 0)
7777+ (String.length (Digest.encoded_hash d) > 0)
8078 | Error (`Msg e) -> fail (Fmt.str "invalid digest: %s" e)
81798280(* {1 Manifest parsing} *)
···8583 let raw = read_file "manifest.json" in
8684 match Manifest.of_string raw with
8785 | Error (`Msg e) -> fail e
8888- | Ok manifest -> (
8989- match manifest with
9090- | `OCI_manifest m ->
9191- let layers = Manifest.OCI.layers m in
9292- check bool "has layers" true (List.length layers > 0);
9393- let config = Manifest.OCI.config m in
9494- let config_mt = Descriptor.media_type config in
9595- check string "config media type"
9696- "application/vnd.oci.image.config.v1+json"
9797- (Media_type.to_string config_mt)
9898- | `Docker_manifest m ->
9999- let layers = Manifest.Docker.layers m in
100100- check bool "has layers" true (List.length layers > 0);
101101- let config = Manifest.Docker.config m in
102102- let config_mt = Descriptor.media_type config in
103103- check string "config media type"
104104- "application/vnd.docker.container.image.v1+json"
105105- (Media_type.to_string config_mt)
106106- | _ -> fail "expected single-platform manifest")
8686+ | Ok (`OCI_manifest m) ->
8787+ let layers = Manifest.OCI.layers m in
8888+ check bool "has layers" true (List.length layers > 0);
8989+ let config = Manifest.OCI.config m in
9090+ let config_mt = Descriptor.media_type config in
9191+ check string "config media type"
9292+ "application/vnd.oci.image.config.v1+json"
9393+ (Media_type.to_string config_mt)
9494+ | Ok (`Docker_manifest m) ->
9595+ let layers = Manifest.Docker.layers m in
9696+ check bool "has layers" true (List.length layers > 0);
9797+ let config = Manifest.Docker.config m in
9898+ let config_mt = Descriptor.media_type config in
9999+ check string "config media type"
100100+ "application/vnd.docker.container.image.v1+json"
101101+ (Media_type.to_string config_mt)
102102+ | Ok _ -> fail "expected single-platform manifest"
107103108104let test_manifest_config_digest () =
109109- let meta_json = read_file "meta.json" in
110110- let expected =
111111- match Jsont_bytesrw.decode_string Jsont.json_value meta_json with
112112- | Ok (Jsont.Object (members, _)) -> (
113113- match List.find_opt (fun (k, _) -> k = "config_digest") members with
114114- | Some (_, Jsont.String (s, _)) -> s
115115- | _ -> "")
116116- | _ -> ""
117117- in
105105+ let expected = meta_field "config_digest" in
118106 let raw = read_file "manifest.json" in
119107 match Manifest.of_string raw with
120108 | Error (`Msg e) -> fail e
121121- | Ok manifest ->
122122- let config_desc =
123123- match manifest with
124124- | `OCI_manifest m -> Manifest.OCI.config m
125125- | `Docker_manifest m -> Manifest.Docker.config m
126126- | _ ->
127127- fail "expected single-platform manifest";
128128- Descriptor.empty
129129- in
130130- let digest = Descriptor.digest config_desc in
109109+ | Ok (`OCI_manifest m) ->
110110+ let digest = Descriptor.digest (Manifest.OCI.config m) in
131111 check string "config digest" expected (Digest.to_string digest)
112112+ | Ok (`Docker_manifest m) ->
113113+ let digest = Descriptor.digest (Manifest.Docker.config m) in
114114+ check string "config digest" expected (Digest.to_string digest)
115115+ | Ok _ -> fail "expected single-platform manifest"
132116133117(* {1 Config parsing} *)
134118135119let test_parse_config () =
136120 let raw = read_file "config.json" in
137137- match Config.of_string raw with
121121+ match Config.OCI.of_string raw with
138122 | Error (`Msg e) -> fail e
139123 | Ok config ->
140140- let platform = Config.platform config in
124124+ let platform = Config.OCI.platform config in
141125 check string "os" "linux" (OS.to_string (Platform.os platform));
142126 check string "arch" "amd64" (Arch.to_string (Platform.arch platform))
143127144128(* {1 Round-trip} *)
145129146130let test_index_roundtrip () =
147147- let json = read_json "index.json" in
148148- match Index.of_yojson json with
149149- | Error e -> fail e
150150- | Ok index -> (
131131+ let raw = read_file "index.json" in
132132+ match Manifest.of_string raw with
133133+ | Error (`Msg e) -> fail e
134134+ | Ok (`OCI_index index) -> (
151135 let serialized = Index.to_string index in
152152- (* Parse back and verify key properties are preserved *)
153136 match Manifest.of_string serialized with
154137 | Error (`Msg e) -> fail (Fmt.str "round-trip parse failed: %s" e)
155138 | Ok (`OCI_index index2) ->
···157140 (List.length (Index.manifests index))
158141 (List.length (Index.manifests index2))
159142 | Ok _ -> fail "round-trip changed manifest type")
143143+ | Ok _ -> fail "expected OCI index"
160144161145let test_manifest_roundtrip () =
162146 let raw = read_file "manifest.json" in
···176160let test_index_v () =
177161 let desc =
178162 Descriptor.v
179179- ~platform:(Platform.v Arch.Amd64 OS.Linux)
163163+ ~platform:(Platform.v Arch.Xamd64 OS.Linux)
180164 ~annotations:
181165 [
182166 (Annotation.Other "dev.spaceos.partition.name", "p0");
···193177 [ desc ]
194178 in
195179 let json = Index.to_string index in
196196- (* Parse it back *)
197180 match Manifest.of_string json with
198181 | Error (`Msg e) -> fail (Fmt.str "Index.v round-trip failed: %s" e)
199182 | Ok (`OCI_index index2) ->
···204187 in
205188 check (option string) "kernel annotation" (Some "linuxkit/kernel:6.6")
206189 kernel;
207207- (* Check descriptor annotations survived *)
208190 let desc2 = List.hd (Index.manifests index2) in
209191 let desc_annots = Descriptor.annotations desc2 in
210192 let part_name =
···222204 ( "index",
223205 [
224206 test_case "parse" `Quick test_parse_index;
207207+ test_case "annotations" `Quick test_index_annotations;
225208 test_case "digest" `Quick test_index_digest;
226209 test_case "roundtrip" `Quick test_index_roundtrip;
227210 test_case "Index.v constructor" `Quick test_index_v;