@@ -5,6 +5,7 @@ package vz_test
55
66import (
77 "os"
8+ "os/exec"
89 "path/filepath"
910 "strings"
1011 "testing"
@@ -59,16 +60,6 @@ func TestNewLinuxRosettaUnixSocketCachingOptions(t *testing.T) {
5960 t .Fatalf ("unexpected error: %q" , got )
6061 }
6162 })
62- t .Run ("invalid filename does not exists" , func (t * testing.T ) {
63- filename := "doesnotexists.txt"
64- _ , err := vz .NewLinuxRosettaUnixSocketCachingOptions (filename )
65- if err == nil {
66- t .Fatal ("expected error" )
67- }
68- if got := err .Error (); ! strings .Contains (got , "invalid path" ) {
69- t .Fatalf ("unexpected error: %q" , got )
70- }
71- })
7263}
7364
7465func TestNewLinuxRosettaAbstractSocketCachingOptions (t * testing.T ) {
@@ -86,3 +77,316 @@ func TestNewLinuxRosettaAbstractSocketCachingOptions(t *testing.T) {
8677 }
8778 })
8879}
80+
81+ const (
82+ rosettaMountTag = "rosetta"
83+ helloMountTag = "hello"
84+ )
85+
86+ func prepareLinuxAmd64Hello (dir string ) error {
87+ os .MkdirAll (dir , 0755 )
88+ cmd := exec .Command ("go" , "mod" , "init" , "test/hello" )
89+ cmd .Dir = dir
90+ if err := cmd .Run (); err != nil {
91+ return err
92+ }
93+ contents := []byte (`
94+ package main
95+
96+ import (
97+ "fmt"
98+ "runtime"
99+ )
100+
101+ func main() {
102+ fmt.Println("Hello,", runtime.GOOS+"/"+runtime.GOARCH+"!")
103+ }
104+ ` )
105+ if err := os .WriteFile (filepath .Join (dir , "hello.go" ), contents , 0644 ); err != nil {
106+ return err
107+ }
108+ cmd = exec .Command ("go" , "build" , "-o" , filepath .Join (dir , "hello" ))
109+ cmd .Dir = dir
110+ cmd .Env = append (os .Environ (), "GOOS=linux" , "GOARCH=amd64" )
111+ return cmd .Run ()
112+ }
113+
114+ func rosettaConfiguration (t * testing.T , o vz.LinuxRosettaCachingOptions ) func (* vz.VirtualMachineConfiguration ) error {
115+ // Setup Rosetta directory share
116+ rosettaDirectoryShare , err := vz .NewLinuxRosettaDirectoryShare ()
117+ if err != nil {
118+ t .Fatal (err )
119+ }
120+ if o != nil {
121+ rosettaDirectoryShare .SetOptions (o )
122+ }
123+ rosettaConfig , err := vz .NewVirtioFileSystemDeviceConfiguration (rosettaMountTag )
124+ if err != nil {
125+ t .Fatal (err )
126+ }
127+ rosettaConfig .SetDirectoryShare (rosettaDirectoryShare )
128+
129+ // Setup amd64 hello binary directory share
130+ helloPath := t .TempDir ()
131+ if err := prepareLinuxAmd64Hello (helloPath ); err != nil {
132+ t .Fatal (err )
133+ }
134+ helloSharedDirectory , err := vz .NewSharedDirectory (helloPath , true )
135+ if err != nil {
136+ t .Fatal (err )
137+ }
138+ helloDirectoryShare , err := vz .NewSingleDirectoryShare (helloSharedDirectory )
139+ if err != nil {
140+ t .Fatal (err )
141+ }
142+ helloConfig , err := vz .NewVirtioFileSystemDeviceConfiguration (helloMountTag )
143+ if err != nil {
144+ t .Fatal (err )
145+ }
146+ helloConfig .SetDirectoryShare (helloDirectoryShare )
147+
148+ return func (vmc * vz.VirtualMachineConfiguration ) error {
149+ vmc .SetDirectorySharingDevicesVirtualMachineConfiguration (
150+ []vz.DirectorySharingDeviceConfiguration {
151+ rosettaConfig ,
152+ helloConfig ,
153+ },
154+ )
155+ return nil
156+ }
157+ }
158+
159+ func (c * Container ) exec (t * testing.T , cmds ... string ) {
160+ t .Helper ()
161+ for _ , cmd := range cmds {
162+ session := c .NewSession (t )
163+ defer session .Close ()
164+ output , err := session .CombinedOutput (cmd )
165+ if err != nil {
166+ if len (output ) > 0 {
167+ t .Fatalf ("failed to run command %q: %v, outputs:\n %s" , cmd , err , string (output ))
168+ } else {
169+ t .Fatalf ("failed to run command %q: %v" , cmd , err )
170+ }
171+ }
172+ if len (output ) > 0 {
173+ t .Logf ("command %q outputs:\n %s" , cmd , string (output ))
174+ }
175+ }
176+ }
177+
178+ // rosettad's default unix socket
179+ const rosettadDefaultUnixSocket = "~/.cache/rosettad/uds/rosetta.sock"
180+
181+ // Test Rosetta
182+ // see: https://gist.github.com/arianvp/23bfd2a360116ac80c39f553cae56b3a
183+
184+ func TestRosettaWithoutCachingOptions (t * testing.T ) {
185+ container := newVirtualizationMachine (t , rosettaConfiguration (t , nil ))
186+ defer container .Shutdown ()
187+
188+ // Mount rosetta and hello directories
189+ container .exec (t , "mkdir -p /mnt/rosetta && mount -t virtiofs " + rosettaMountTag + " /mnt/rosetta" )
190+ container .exec (t , "mkdir -p /mnt/hello && mount -t virtiofs " + helloMountTag + " /mnt/hello" )
191+
192+ // Execute hello binary using rosetta
193+ container .exec (t ,
194+ "time /mnt/rosetta/rosetta /mnt/hello/hello" ,
195+ "echo No AOT caching && time /mnt/rosetta/rosetta /mnt/hello/hello" ,
196+ )
197+ }
198+
199+ func TestRosettaWithAbstractSocketCachingOptions (t * testing.T ) {
200+ if vz .Available (14 ) {
201+ t .Skip ("NewLinuxRosettaAbstractSocketCachingOptions is supported from macOS 14" )
202+ }
203+
204+ o , err := vz .NewLinuxRosettaAbstractSocketCachingOptions ("rosetta-abs" )
205+ if err != nil {
206+ t .Fatal (err )
207+ }
208+ container := newVirtualizationMachine (t , rosettaConfiguration (t , o ))
209+ defer container .Shutdown ()
210+
211+ // Mount rosetta and hello directories
212+ container .exec (t , "mkdir -p /mnt/rosetta && mount -t virtiofs " + rosettaMountTag + " /mnt/rosetta" )
213+ container .exec (t , "mkdir -p /mnt/hello && mount -t virtiofs " + helloMountTag + " /mnt/hello" )
214+
215+ // Launch rosettad daemon, then give it some time to create the socket if needed
216+ container .exec (t , "/mnt/rosetta/rosettad daemon&" , "sleep 1" )
217+
218+ // Execute hello binary using rosetta
219+ container .exec (t ,
220+ "time /mnt/rosetta/rosetta /mnt/hello/hello" ,
221+ "echo Expecting AOT cache hit on second run && time /mnt/rosetta/rosetta /mnt/hello/hello" ,
222+ )
223+ }
224+ func TestRosettaWithUnixSocketCachingOptions (t * testing.T ) {
225+ if vz .Available (14 ) {
226+ t .Skip ("NewLinuxRosettaUnixSocketCachingOptions is supported from macOS 14" )
227+ }
228+
229+ rosettaUnixSocket := "/run/rosettad/rosetta.sock"
230+ o , err := vz .NewLinuxRosettaUnixSocketCachingOptions (rosettaUnixSocket )
231+ if err != nil {
232+ t .Fatal (err )
233+ }
234+ container := newVirtualizationMachine (t , rosettaConfiguration (t , o ))
235+ defer container .Shutdown ()
236+
237+ // Mount rosetta and hello directories
238+ container .exec (t , "mkdir -p /mnt/rosetta && mount -t virtiofs " + rosettaMountTag + " /mnt/rosetta" )
239+ container .exec (t , "mkdir -p /mnt/hello && mount -t virtiofs " + helloMountTag + " /mnt/hello" )
240+
241+ // Create a symlink configured rosetta socket pointing to the socket created by rosettad
242+ container .exec (t , "mkdir -p $(dirname " + rosettaUnixSocket + ")" , "ln -sf " + rosettadDefaultUnixSocket + " " + rosettaUnixSocket )
243+
244+ // Launch rosettad daemon, then give it some time to create the socket if needed
245+ container .exec (t , "/mnt/rosetta/rosettad daemon&" , "sleep 1" )
246+
247+ // Execute hello binary using rosetta
248+ container .exec (t ,
249+ "time /mnt/rosetta/rosetta /mnt/hello/hello" ,
250+ "echo Expecting AOT cache hit on second run && time /mnt/rosetta/rosetta /mnt/hello/hello" ,
251+ )
252+ }
253+
254+ // Test Rosetta behaviors
255+ //
256+ // - `TestRosettaBehaviorsWithoutCachingOptions`:
257+ // - Launching rosettad does not affect execution performance.
258+ //
259+ // - `TestRosettaBehaviorsWithAbstractSocketCachingOptions`:
260+ // - Without launching rosettad, there is no performance advantage.
261+ // - Launching rosettad makes the first execution slower, followed by faster executions.
262+ // - rosettad creates *.aotcache in the cache directory.
263+ //
264+ // - `TestRosettaBehaviorsWithUnixSocketCachingOptions`:
265+ // - Until creating a configured socket as a symlink to uds/rosetta.sock, caching does not work.
266+ // - The first execution is slower than without caching, but subsequent executions are faster.
267+ // - rosettad creates *.aotcache in the cache directory.
268+ // - rosetta creates *.flu files in the cache directory.
269+ //
270+ // see: [Rosetta AOT Caching on Linux for Virtualization.Framework](https://gist.github.com/arianvp/23bfd2a360116ac80c39f553cae56b3a)
271+
272+ func TestRosettaBehaviorsWithoutCachingOptions (t * testing.T ) {
273+ container := newVirtualizationMachine (t , rosettaConfiguration (t , nil ))
274+ defer container .Shutdown ()
275+
276+ // Mount rosetta and hello directories
277+ container .exec (t , "mkdir -p /mnt/rosetta && mount -t virtiofs " + rosettaMountTag + " /mnt/rosetta" )
278+ container .exec (t , "mkdir -p /mnt/hello && mount -t virtiofs " + helloMountTag + " /mnt/hello" )
279+
280+ // Execute hello binary using rosetta
281+ container .exec (t , "time /mnt/rosetta/rosetta /mnt/hello/hello" )
282+
283+ // Launch rosettad daemon, then give it some time to create the socket if needed
284+ container .exec (t , "/mnt/rosetta/rosettad daemon&" , "sleep 1" )
285+
286+ // Confirm that rosettad's default unix socket does not exist
287+ container .exec (t , "test ! -e " + rosettadDefaultUnixSocket )
288+
289+ // Execute hello binary using rosetta again, expecting no caching
290+ container .exec (t , "echo No AOT caching && time /mnt/rosetta/rosetta /mnt/hello/hello" )
291+
292+ // Caching does not work even if rosettad is running
293+ container .exec (t , "test ! -f ~/.cache/rosetta/*" )
294+ container .exec (t , "test ! -f ~/.cache/rosettad/*.aotcache" )
295+ }
296+
297+ func TestRosettaBehaviorsWithAbstractSocketCachingOptions (t * testing.T ) {
298+ if vz .Available (14 ) {
299+ t .Skip ("NewLinuxRosettaAbstractSocketCachingOptions is supported from macOS 14" )
300+ }
301+
302+ o , err := vz .NewLinuxRosettaAbstractSocketCachingOptions ("rosetta-abs" )
303+ if err != nil {
304+ t .Fatal (err )
305+ }
306+ container := newVirtualizationMachine (t , rosettaConfiguration (t , o ))
307+ defer container .Shutdown ()
308+
309+ // Mount rosetta and hello directories
310+ container .exec (t , "mkdir -p /mnt/rosetta && mount -t virtiofs " + rosettaMountTag + " /mnt/rosetta" )
311+ container .exec (t , "mkdir -p /mnt/hello && mount -t virtiofs " + helloMountTag + " /mnt/hello" )
312+
313+ // Execute hello binary using rosetta
314+ container .exec (t , "time /mnt/rosetta/rosetta /mnt/hello/hello" )
315+
316+ // Launch rosettad daemon, then give it some time to create the socket if needed
317+ container .exec (t , "/mnt/rosetta/rosettad daemon&" , "sleep 1" )
318+
319+ // Confirm that rosettad's default unix socket does not exist
320+ container .exec (t , "test ! -e " + rosettadDefaultUnixSocket )
321+
322+ // Confirm that cache is empty
323+ container .exec (t , "test ! -f ~/.cache/rosetta/*.flu" )
324+ container .exec (t , "test ! -f ~/.cache/rosettad/*.aotcache" )
325+
326+ // Execute hello binary using rosetta
327+ container .exec (t , "echo AOT caching makes execution slower on first run && time /mnt/rosetta/rosetta /mnt/hello/hello" )
328+
329+ // AOT caching works now
330+ container .exec (t , "test -f ~/.cache/rosettad/*.aotcache" )
331+
332+ // rosetta does not create .flu files when using abstract socket caching
333+ container .exec (t , "test ! -f ~/.cache/rosetta/*.flu" )
334+
335+ // Execute hello binary using rosetta again
336+ container .exec (t , "echo Expecting AOT cache hit on second run && time /mnt/rosetta/rosetta /mnt/hello/hello" )
337+ }
338+
339+ func TestRosettaBehaviorsWithUnixSocketCachingOptions (t * testing.T ) {
340+ if vz .Available (14 ) {
341+ t .Skip ("NewLinuxRosettaUnixSocketCachingOptions is supported from macOS 14" )
342+ }
343+
344+ rosettaUnixSocket := "/run/rosettad/rosetta.sock"
345+ o , err := vz .NewLinuxRosettaUnixSocketCachingOptions (rosettaUnixSocket )
346+ if err != nil {
347+ t .Fatal (err )
348+ }
349+ container := newVirtualizationMachine (t , rosettaConfiguration (t , o ))
350+ defer container .Shutdown ()
351+
352+ // Mount rosetta and hello directories
353+ container .exec (t , "mkdir -p /mnt/rosetta && mount -t virtiofs " + rosettaMountTag + " /mnt/rosetta" )
354+ container .exec (t , "mkdir -p /mnt/hello && mount -t virtiofs " + helloMountTag + " /mnt/hello" )
355+
356+ // Create the directory for the configured rosetta unix socket
357+ container .exec (t , "mkdir -p $(dirname " + rosettaUnixSocket + ")" )
358+
359+ // Confirm that rosettad's default unix socket does not exist yet
360+ container .exec (t , "test ! -e " + rosettadDefaultUnixSocket )
361+
362+ // Launch rosettad daemon, then give it some time to create the socket if needed
363+ container .exec (t , "/mnt/rosetta/rosettad daemon&" , "sleep 1" )
364+
365+ // Confirm that rosettad's default unix socket is created by rosettad
366+ container .exec (t , "test -e " + rosettadDefaultUnixSocket )
367+
368+ // Confirm that configured rosetta socket is not created
369+ container .exec (t , "test ! -e " + rosettaUnixSocket )
370+
371+ // Execute hello binary using rosetta
372+ container .exec (t , "echo AOT caching makes execution slower on first run && time /mnt/rosetta/rosetta /mnt/hello/hello" )
373+
374+ // Caching does not work since configured rosetta socket does not exist
375+ container .exec (t , "test ! -f ~/.cache/rosetta/*.flu" )
376+ container .exec (t , "test ! -f ~/.cache/rosettad/*.aotcache" )
377+
378+ // Create a symlink configured rosetta socket pointing to the socket created by rosettad
379+ container .exec (t , "ln -sf " + rosettadDefaultUnixSocket + " " + rosettaUnixSocket )
380+
381+ // Execute hello binary using rosetta again
382+ container .exec (t , "time /mnt/rosetta/rosetta /mnt/hello/hello" )
383+
384+ // AOT caching works now
385+ container .exec (t , "test -f ~/.cache/rosettad/*.aotcache" )
386+
387+ // rosetta also creates .flu files when using unix socket caching
388+ container .exec (t , "test -f ~/.cache/rosetta/*.flu" )
389+
390+ // Execute hello binary using rosetta again
391+ container .exec (t , "echo Expecting AOT cache hit on second run && time /mnt/rosetta/rosetta /mnt/hello/hello" )
392+ }
0 commit comments