···11-(** Docker Hub registry interop tests for ocaml-oci.
22-33- Traces generated by: Docker Hub registry API (library/alpine:3.19.4)
44- Regenerate: dune build @regen-traces *)
55-66-open Alcotest
77-open Oci_spec
88-99-let case = test_case
1010-1111-let trace path = Filename.concat "traces" path
1212-1313-let read_file path =
1414- let ic = open_in (trace path) in
1515- let s = In_channel.input_all ic in
1616- close_in ic;
1717- s
1818-1919-let meta_jsont =
2020- Jsont.Object.map (fun index_digest manifest_digest config_digest ->
2121- (index_digest, manifest_digest, config_digest))
2222- |> Jsont.Object.mem "index_digest" Jsont.string ~enc:(fun (x, _, _) -> x)
2323- |> Jsont.Object.mem "manifest_digest" Jsont.string ~enc:(fun (_, x, _) -> x)
2424- |> Jsont.Object.mem "config_digest" Jsont.string ~enc:(fun (_, _, x) -> x)
2525- |> Jsont.Object.skip_unknown |> Jsont.Object.finish
2626-2727-let meta () =
2828- let raw = read_file "meta.json" in
2929- match Jsont_bytesrw.decode_string meta_jsont raw with
3030- | Ok m -> m
3131- | Error e -> Fmt.failwith "meta.json: %s" e
3232-3333-(* {1 Index parsing} *)
3434-3535-let parse_index () =
3636- let raw = read_file "index.json" in
3737- match Manifest.of_string raw with
3838- | Error (`Msg e) -> fail e
3939- | Ok (`OCI_index index) ->
4040- let manifests = Index.manifests index in
4141- check bool "has manifests" true (List.length manifests > 0);
4242- check bool "multiple manifests" true (List.length manifests >= 7);
4343- let has_amd64 =
4444- List.exists
4545- (fun d ->
4646- match Descriptor.platform d with
4747- | Some p ->
4848- Arch.to_string (Platform.arch p) = "amd64"
4949- && OS.to_string (Platform.os p) = "linux"
5050- | None -> false)
5151- manifests
5252- in
5353- check bool "has linux/amd64" true has_amd64
5454- | Ok _ -> fail "expected OCI index"
5555-5656-let index_annotations () =
5757- let raw = read_file "index.json" in
5858- match Manifest.of_string raw with
5959- | Error (`Msg e) -> fail e
6060- | Ok (`OCI_index index) ->
6161- let manifests = Index.manifests index in
6262- let first = List.hd manifests in
6363- let annots = Descriptor.annotations first in
6464- check bool "descriptor has annotations" true (List.length annots > 0);
6565- let has_version =
6666- List.exists (fun (k, _) -> k = Annotation.Version) annots
6767- in
6868- check bool "has version annotation" true has_version
6969- | Ok _ -> fail "expected OCI index"
7070-7171-let index_digest () =
7272- let expected, _, _ = meta () in
7373- match Digest.of_string expected with
7474- | Ok d ->
7575- check string "algorithm" "sha256"
7676- (Digest.string_of_algorithm (Digest.algorithm d));
7777- check bool "non-empty hash" true
7878- (String.length (Digest.encoded_hash d) > 0)
7979- | Error (`Msg e) -> fail (Fmt.str "invalid digest: %s" e)
8080-8181-(* {1 Manifest parsing} *)
8282-8383-let parse_manifest () =
8484- let raw = read_file "manifest.json" in
8585- match Manifest.of_string raw with
8686- | Error (`Msg e) -> fail e
8787- | Ok (`OCI_manifest m) ->
8888- let layers = Manifest.OCI.layers m in
8989- check bool "has layers" true (List.length layers > 0);
9090- let config = Manifest.OCI.config m in
9191- let config_mt = Descriptor.media_type config in
9292- check string "config media type"
9393- "application/vnd.oci.image.config.v1+json"
9494- (Media_type.to_string config_mt)
9595- | Ok (`Docker_manifest m) ->
9696- let layers = Manifest.Docker.layers m in
9797- check bool "has layers" true (List.length layers > 0);
9898- let config = Manifest.Docker.config m in
9999- let config_mt = Descriptor.media_type config in
100100- check string "config media type"
101101- "application/vnd.docker.container.image.v1+json"
102102- (Media_type.to_string config_mt)
103103- | Ok _ -> fail "expected single-platform manifest"
104104-105105-let manifest_config_digest () =
106106- let _, _, expected = meta () in
107107- let raw = read_file "manifest.json" in
108108- match Manifest.of_string raw with
109109- | Error (`Msg e) -> fail e
110110- | Ok (`OCI_manifest m) ->
111111- let digest = Descriptor.digest (Manifest.OCI.config m) in
112112- check string "config digest" expected (Digest.to_string digest)
113113- | Ok (`Docker_manifest m) ->
114114- let digest = Descriptor.digest (Manifest.Docker.config m) in
115115- check string "config digest" expected (Digest.to_string digest)
116116- | Ok _ -> fail "expected single-platform manifest"
117117-118118-(* {1 Config parsing} *)
119119-120120-let parse_config () =
121121- let raw = read_file "config.json" in
122122- match Config.OCI.of_string raw with
123123- | Error (`Msg e) -> fail e
124124- | Ok config ->
125125- let platform = Config.OCI.platform config in
126126- check string "os" "linux" (OS.to_string (Platform.os platform));
127127- check string "arch" "amd64" (Arch.to_string (Platform.arch platform))
128128-129129-(* {1 Round-trip} *)
130130-131131-let index_roundtrip () =
132132- let raw = read_file "index.json" in
133133- match Manifest.of_string raw with
134134- | Error (`Msg e) -> fail e
135135- | Ok (`OCI_index index) -> (
136136- let serialized = Index.to_string index in
137137- match Manifest.of_string serialized with
138138- | Error (`Msg e) -> fail (Fmt.str "round-trip parse failed: %s" e)
139139- | Ok (`OCI_index index2) ->
140140- check int "manifest count"
141141- (List.length (Index.manifests index))
142142- (List.length (Index.manifests index2))
143143- | Ok _ -> fail "round-trip changed manifest type")
144144- | Ok _ -> fail "expected OCI index"
145145-146146-let manifest_roundtrip () =
147147- let raw = read_file "manifest.json" in
148148- match Manifest.of_string raw with
149149- | Error (`Msg e) -> fail e
150150- | Ok manifest -> (
151151- let serialized = Manifest.to_string manifest in
152152- match Manifest.of_string serialized with
153153- | Error (`Msg e) -> fail (Fmt.str "round-trip parse failed: %s" e)
154154- | Ok manifest2 ->
155155- let size1 = Manifest.size manifest in
156156- let size2 = Manifest.size manifest2 in
157157- check (option int64) "size preserved" size1 size2)
158158-159159-(* {1 Index.v constructor} *)
160160-161161-let index_v () =
162162- let desc =
163163- Descriptor.v
164164- ~platform:(Platform.v Arch.Amd64 OS.Linux)
165165- ~annotations:
166166- [
167167- (Annotation.Other "dev.spaceos.partition.name", "p0");
168168- (Annotation.Other "dev.spaceos.partition.init", "pid1");
169169- ]
170170- ~media_type:(OCI Image_manifest) ~size:1234L
171171- (Digest.sha256
172172- "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
173173- in
174174- let index =
175175- Index.v ~artifact_type:"application/vnd.spaceos.build.v1"
176176- ~annotations:
177177- [ (Annotation.Other "dev.spaceos.build.kernel", "linuxkit/kernel:6.6") ]
178178- [ desc ]
179179- in
180180- let json = Index.to_string index in
181181- match Manifest.of_string json with
182182- | Error (`Msg e) -> fail (Fmt.str "Index.v round-trip failed: %s" e)
183183- | Ok (`OCI_index index2) ->
184184- check int "manifests" 1 (List.length (Index.manifests index2));
185185- let annots = Index.annotations index2 in
186186- let kernel =
187187- List.assoc_opt (Annotation.Other "dev.spaceos.build.kernel") annots
188188- in
189189- check (option string) "kernel annotation" (Some "linuxkit/kernel:6.6")
190190- kernel;
191191- let desc2 = List.hd (Index.manifests index2) in
192192- let desc_annots = Descriptor.annotations desc2 in
193193- let part_name =
194194- List.assoc_opt (Annotation.Other "dev.spaceos.partition.name")
195195- desc_annots
196196- in
197197- check (option string) "partition name" (Some "p0") part_name
198198- | Ok _ -> fail "expected OCI_index"
199199-200200-(* {1 Test runner} *)
201201-2021let () =
203203- run "oci-registry-interop"
204204- [
205205- ( "index",
206206- [
207207- case "parse" `Quick parse_index;
208208- case "annotations" `Quick index_annotations;
209209- case "digest" `Quick index_digest;
210210- case "roundtrip" `Quick index_roundtrip;
211211- case "Index.v constructor" `Quick index_v;
212212- ] );
213213- ( "manifest",
214214- [
215215- case "parse" `Quick parse_manifest;
216216- case "config digest" `Quick manifest_config_digest;
217217- case "roundtrip" `Quick manifest_roundtrip;
218218- ] );
219219- ("config", [ case "parse" `Quick parse_config ]);
220220- ]
22+ Alcotest.run "oci-registry-interop"
33+ [ Test_index.suite; Test_manifest.suite; Test_config.suite ]
+14
test/interop/registry/test_config.ml
···11+open Oci_spec
22+33+let parse () =
44+ let raw = Test_helpers.read_file "config.json" in
55+ match Config.OCI.of_string raw with
66+ | Error (`Msg e) -> Alcotest.fail e
77+ | Ok config ->
88+ let platform = Config.OCI.platform config in
99+ Alcotest.(check string) "os" "linux" (OS.to_string (Platform.os platform));
1010+ Alcotest.(check string)
1111+ "arch" "amd64"
1212+ (Arch.to_string (Platform.arch platform))
1313+1414+let suite = ("config", [ Alcotest.test_case "parse" `Quick parse ])
+1
test/interop/registry/test_config.mli
···11+val suite : string * unit Alcotest.test_case list
+21
test/interop/registry/test_helpers.ml
···11+let trace path = Filename.concat "traces" path
22+33+let read_file path =
44+ let ic = open_in (trace path) in
55+ let s = In_channel.input_all ic in
66+ close_in ic;
77+ s
88+99+let meta_jsont =
1010+ Jsont.Object.map (fun index_digest manifest_digest config_digest ->
1111+ (index_digest, manifest_digest, config_digest))
1212+ |> Jsont.Object.mem "index_digest" Jsont.string ~enc:(fun (x, _, _) -> x)
1313+ |> Jsont.Object.mem "manifest_digest" Jsont.string ~enc:(fun (_, x, _) -> x)
1414+ |> Jsont.Object.mem "config_digest" Jsont.string ~enc:(fun (_, _, x) -> x)
1515+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
1616+1717+let meta () =
1818+ let raw = read_file "meta.json" in
1919+ match Jsont_bytesrw.decode_string meta_jsont raw with
2020+ | Ok m -> m
2121+ | Error e -> Fmt.failwith "meta.json: %s" e
+2
test/interop/registry/test_helpers.mli
···11+val read_file : string -> string
22+val meta : unit -> string * string * string
+116
test/interop/registry/test_index.ml
···11+open Oci_spec
22+33+let parse () =
44+ let raw = Test_helpers.read_file "index.json" in
55+ match Manifest.of_string raw with
66+ | Error (`Msg e) -> Alcotest.fail e
77+ | Ok (`OCI_index index) ->
88+ let manifests = Index.manifests index in
99+ Alcotest.(check bool) "has manifests" true (List.length manifests > 0);
1010+ Alcotest.(check bool)
1111+ "multiple manifests" true
1212+ (List.length manifests >= 7);
1313+ let has_amd64 =
1414+ List.exists
1515+ (fun d ->
1616+ match Descriptor.platform d with
1717+ | Some p ->
1818+ Arch.to_string (Platform.arch p) = "amd64"
1919+ && OS.to_string (Platform.os p) = "linux"
2020+ | None -> false)
2121+ manifests
2222+ in
2323+ Alcotest.(check bool) "has linux/amd64" true has_amd64
2424+ | Ok _ -> Alcotest.fail "expected OCI index"
2525+2626+let annotations () =
2727+ let raw = Test_helpers.read_file "index.json" in
2828+ match Manifest.of_string raw with
2929+ | Error (`Msg e) -> Alcotest.fail e
3030+ | Ok (`OCI_index index) ->
3131+ let manifests = Index.manifests index in
3232+ let first = List.hd manifests in
3333+ let annots = Descriptor.annotations first in
3434+ Alcotest.(check bool) "has annotations" true (List.length annots > 0);
3535+ let has_version =
3636+ List.exists (fun (k, _) -> k = Annotation.Version) annots
3737+ in
3838+ Alcotest.(check bool) "has version annotation" true has_version
3939+ | Ok _ -> Alcotest.fail "expected OCI index"
4040+4141+let digest () =
4242+ let expected, _, _ = Test_helpers.meta () in
4343+ match Digest.of_string expected with
4444+ | Ok d ->
4545+ Alcotest.(check string)
4646+ "algorithm" "sha256"
4747+ (Digest.string_of_algorithm (Digest.algorithm d));
4848+ Alcotest.(check bool)
4949+ "non-empty hash" true
5050+ (String.length (Digest.encoded_hash d) > 0)
5151+ | Error (`Msg e) -> Alcotest.fail (Fmt.str "invalid digest: %s" e)
5252+5353+let roundtrip () =
5454+ let raw = Test_helpers.read_file "index.json" in
5555+ match Manifest.of_string raw with
5656+ | Error (`Msg e) -> Alcotest.fail e
5757+ | Ok (`OCI_index index) -> (
5858+ let serialized = Index.to_string index in
5959+ match Manifest.of_string serialized with
6060+ | Error (`Msg e) -> Alcotest.fail (Fmt.str "round-trip failed: %s" e)
6161+ | Ok (`OCI_index index2) ->
6262+ Alcotest.(check int)
6363+ "manifest count"
6464+ (List.length (Index.manifests index))
6565+ (List.length (Index.manifests index2))
6666+ | Ok _ -> Alcotest.fail "round-trip changed type")
6767+ | Ok _ -> Alcotest.fail "expected OCI index"
6868+6969+let v_constructor () =
7070+ let desc =
7171+ Descriptor.v
7272+ ~platform:(Platform.v Arch.Amd64 OS.Linux)
7373+ ~annotations:
7474+ [
7575+ (Annotation.Other "dev.spaceos.partition.name", "p0");
7676+ (Annotation.Other "dev.spaceos.partition.init", "pid1");
7777+ ]
7878+ ~media_type:(OCI Image_manifest) ~size:1234L
7979+ (Digest.sha256
8080+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
8181+ in
8282+ let index =
8383+ Index.v ~artifact_type:"application/vnd.spaceos.build.v1"
8484+ ~annotations:
8585+ [ (Annotation.Other "dev.spaceos.build.kernel", "linuxkit/kernel:6.6") ]
8686+ [ desc ]
8787+ in
8888+ let json = Index.to_string index in
8989+ match Manifest.of_string json with
9090+ | Error (`Msg e) -> Alcotest.fail (Fmt.str "Index.v round-trip failed: %s" e)
9191+ | Ok (`OCI_index index2) ->
9292+ Alcotest.(check int) "manifests" 1 (List.length (Index.manifests index2));
9393+ let annots = Index.annotations index2 in
9494+ let kernel =
9595+ List.assoc_opt (Annotation.Other "dev.spaceos.build.kernel") annots
9696+ in
9797+ Alcotest.(check (option string))
9898+ "kernel annotation" (Some "linuxkit/kernel:6.6") kernel;
9999+ let desc2 = List.hd (Index.manifests index2) in
100100+ let desc_annots = Descriptor.annotations desc2 in
101101+ let part_name =
102102+ List.assoc_opt (Annotation.Other "dev.spaceos.partition.name")
103103+ desc_annots
104104+ in
105105+ Alcotest.(check (option string)) "partition name" (Some "p0") part_name
106106+ | Ok _ -> Alcotest.fail "expected OCI_index"
107107+108108+let suite =
109109+ ( "index",
110110+ [
111111+ Alcotest.test_case "parse" `Quick parse;
112112+ Alcotest.test_case "annotations" `Quick annotations;
113113+ Alcotest.test_case "digest" `Quick digest;
114114+ Alcotest.test_case "roundtrip" `Quick roundtrip;
115115+ Alcotest.test_case "Index.v constructor" `Quick v_constructor;
116116+ ] )
+1
test/interop/registry/test_index.mli
···11+val suite : string * unit Alcotest.test_case list
+55
test/interop/registry/test_manifest.ml
···11+open Oci_spec
22+33+let parse () =
44+ let raw = Test_helpers.read_file "manifest.json" in
55+ match Manifest.of_string raw with
66+ | Error (`Msg e) -> Alcotest.fail e
77+ | Ok (`OCI_manifest m) ->
88+ let layers = Manifest.OCI.layers m in
99+ Alcotest.(check bool) "has layers" true (List.length layers > 0);
1010+ let config = Manifest.OCI.config m in
1111+ Alcotest.(check string)
1212+ "config media type" "application/vnd.oci.image.config.v1+json"
1313+ (Media_type.to_string (Descriptor.media_type config))
1414+ | Ok (`Docker_manifest m) ->
1515+ let layers = Manifest.Docker.layers m in
1616+ Alcotest.(check bool) "has layers" true (List.length layers > 0);
1717+ let config = Manifest.Docker.config m in
1818+ Alcotest.(check string)
1919+ "config media type" "application/vnd.docker.container.image.v1+json"
2020+ (Media_type.to_string (Descriptor.media_type config))
2121+ | Ok _ -> Alcotest.fail "expected single-platform manifest"
2222+2323+let config_digest () =
2424+ let _, _, expected = Test_helpers.meta () in
2525+ let raw = Test_helpers.read_file "manifest.json" in
2626+ match Manifest.of_string raw with
2727+ | Error (`Msg e) -> Alcotest.fail e
2828+ | Ok (`OCI_manifest m) ->
2929+ let digest = Descriptor.digest (Manifest.OCI.config m) in
3030+ Alcotest.(check string) "config digest" expected (Digest.to_string digest)
3131+ | Ok (`Docker_manifest m) ->
3232+ let digest = Descriptor.digest (Manifest.Docker.config m) in
3333+ Alcotest.(check string) "config digest" expected (Digest.to_string digest)
3434+ | Ok _ -> Alcotest.fail "expected single-platform manifest"
3535+3636+let roundtrip () =
3737+ let raw = Test_helpers.read_file "manifest.json" in
3838+ match Manifest.of_string raw with
3939+ | Error (`Msg e) -> Alcotest.fail e
4040+ | Ok manifest -> (
4141+ let serialized = Manifest.to_string manifest in
4242+ match Manifest.of_string serialized with
4343+ | Error (`Msg e) -> Alcotest.fail (Fmt.str "round-trip failed: %s" e)
4444+ | Ok manifest2 ->
4545+ let size1 = Manifest.size manifest in
4646+ let size2 = Manifest.size manifest2 in
4747+ Alcotest.(check (option int64)) "size preserved" size1 size2)
4848+4949+let suite =
5050+ ( "manifest",
5151+ [
5252+ Alcotest.test_case "parse" `Quick parse;
5353+ Alcotest.test_case "config digest" `Quick config_digest;
5454+ Alcotest.test_case "roundtrip" `Quick roundtrip;
5555+ ] )
+1
test/interop/registry/test_manifest.mli
···11+val suite : string * unit Alcotest.test_case list