Skip to content

Commit 101b5bd

Browse files
committed
test: gate firecracker fork tests behind linux build tag
The two TestForkFirecracker* tests invoke forkInstanceFromStoppedOrStandby which needs a hypervisor VM starter — firecracker is linux-only, so these fail on darwin with 'no VM starter for hypervisor type: firecracker'. Split into _linux_test.go; leave the pure-helper TestInstallForkSharedMemFile_* tests cross-platform.
1 parent 8b0000c commit 101b5bd

2 files changed

Lines changed: 84 additions & 72 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//go:build linux
2+
3+
package instances
4+
5+
import (
6+
"context"
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/kernel/hypeman/lib/hypervisor"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// TestForkFirecrackerSharesMemFile_FromTemplate verifies the end-to-end fork
17+
// path: when the source is a Firecracker Template instance, the fork's
18+
// mem-file is a hardlink to the source's mem-file instead of a copy. This
19+
// preserves the firecracker MAP_PRIVATE COW semantics that let multiple forks
20+
// share the heavy backing file.
21+
func TestForkFirecrackerSharesMemFile_FromTemplate(t *testing.T) {
22+
t.Parallel()
23+
24+
mgr, _ := setupTestManager(t)
25+
ctx := context.Background()
26+
27+
sourceID := "shared-memfile-fc-src"
28+
createStandbySnapshotSourceFixture(t, mgr, sourceID, "shared-memfile-fc-src", hypervisor.TypeFirecracker)
29+
promoteFixtureToTemplate(t, mgr, sourceID)
30+
31+
srcSnapshotDir := mgr.paths.InstanceSnapshotLatest(sourceID)
32+
srcMem := filepath.Join(srcSnapshotDir, templateSharedMemFileName)
33+
require.NoError(t, os.WriteFile(srcMem, []byte("firecracker mem-file contents"), 0o644))
34+
snapshotConfigPath := mgr.paths.InstanceSnapshotConfig(sourceID)
35+
require.NoError(t, os.MkdirAll(filepath.Dir(snapshotConfigPath), 0o755))
36+
require.NoError(t, os.WriteFile(snapshotConfigPath, []byte(`{}`), 0o644))
37+
38+
forked, err := mgr.forkInstanceFromStoppedOrStandby(ctx, sourceID, ForkInstanceRequest{
39+
Name: "shared-memfile-fc-fork",
40+
TargetState: StateStopped,
41+
}, true)
42+
require.NoError(t, err)
43+
require.NotNil(t, forked)
44+
45+
forkMem := filepath.Join(mgr.paths.InstanceSnapshotLatest(forked.Id), templateSharedMemFileName)
46+
info, err := os.Lstat(forkMem)
47+
require.NoError(t, err)
48+
assert.True(t, info.Mode().IsRegular(), "fork mem-file must be a regular file (hardlink) for firecracker fan-out")
49+
assert.True(t, sameInode(t, srcMem, forkMem), "fork mem-file must share the source's inode")
50+
}
51+
52+
// TestForkFirecrackerStandbySourceDoesNotShareMemFile guards the
53+
// non-Template carve-out: forking a plain Standby source must copy the
54+
// mem-file outright. Sharing would let a later RestoreInstance on the source
55+
// mutate the file out from under live forks.
56+
func TestForkFirecrackerStandbySourceDoesNotShareMemFile(t *testing.T) {
57+
t.Parallel()
58+
59+
mgr, _ := setupTestManager(t)
60+
ctx := context.Background()
61+
62+
sourceID := "standby-fork-fc-src"
63+
createStandbySnapshotSourceFixture(t, mgr, sourceID, "standby-fork-fc-src", hypervisor.TypeFirecracker)
64+
65+
srcSnapshotDir := mgr.paths.InstanceSnapshotLatest(sourceID)
66+
srcMem := filepath.Join(srcSnapshotDir, templateSharedMemFileName)
67+
require.NoError(t, os.WriteFile(srcMem, []byte("firecracker mem-file contents"), 0o644))
68+
snapshotConfigPath := mgr.paths.InstanceSnapshotConfig(sourceID)
69+
require.NoError(t, os.MkdirAll(filepath.Dir(snapshotConfigPath), 0o755))
70+
require.NoError(t, os.WriteFile(snapshotConfigPath, []byte(`{}`), 0o644))
71+
72+
forked, err := mgr.forkInstanceFromStoppedOrStandby(ctx, sourceID, ForkInstanceRequest{
73+
Name: "standby-fork-fc-fork",
74+
TargetState: StateStopped,
75+
}, true)
76+
require.NoError(t, err)
77+
require.NotNil(t, forked)
78+
79+
forkMem := filepath.Join(mgr.paths.InstanceSnapshotLatest(forked.Id), templateSharedMemFileName)
80+
info, err := os.Lstat(forkMem)
81+
require.NoError(t, err)
82+
require.True(t, info.Mode().IsRegular(), "standby-source fork mem-file must be a regular file copy")
83+
assert.False(t, sameInode(t, srcMem, forkMem), "standby-source fork mem-file must be a copy, not a hardlink to source")
84+
}

lib/instances/templates_shared_memfile_test.go

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package instances
22

33
import (
4-
"context"
54
"os"
65
"path/filepath"
76
"syscall"
87
"testing"
98

10-
"github.com/kernel/hypeman/lib/hypervisor"
119
"github.com/stretchr/testify/assert"
1210
"github.com/stretchr/testify/require"
1311
)
@@ -61,76 +59,6 @@ func TestInstallForkSharedMemFile_ErrorsWhenSourceMissing(t *testing.T) {
6159
require.Error(t, err)
6260
}
6361

64-
// TestForkFirecrackerSharesMemFile_FromTemplate verifies the end-to-end fork
65-
// path: when the source is a Firecracker Template instance, the fork's
66-
// mem-file is a hardlink to the source's mem-file instead of a copy. This
67-
// preserves the firecracker MAP_PRIVATE COW semantics that let multiple forks
68-
// share the heavy backing file.
69-
func TestForkFirecrackerSharesMemFile_FromTemplate(t *testing.T) {
70-
t.Parallel()
71-
72-
mgr, _ := setupTestManager(t)
73-
ctx := context.Background()
74-
75-
sourceID := "shared-memfile-fc-src"
76-
createStandbySnapshotSourceFixture(t, mgr, sourceID, "shared-memfile-fc-src", hypervisor.TypeFirecracker)
77-
promoteFixtureToTemplate(t, mgr, sourceID)
78-
79-
srcSnapshotDir := mgr.paths.InstanceSnapshotLatest(sourceID)
80-
srcMem := filepath.Join(srcSnapshotDir, templateSharedMemFileName)
81-
require.NoError(t, os.WriteFile(srcMem, []byte("firecracker mem-file contents"), 0o644))
82-
snapshotConfigPath := mgr.paths.InstanceSnapshotConfig(sourceID)
83-
require.NoError(t, os.MkdirAll(filepath.Dir(snapshotConfigPath), 0o755))
84-
require.NoError(t, os.WriteFile(snapshotConfigPath, []byte(`{}`), 0o644))
85-
86-
forked, err := mgr.forkInstanceFromStoppedOrStandby(ctx, sourceID, ForkInstanceRequest{
87-
Name: "shared-memfile-fc-fork",
88-
TargetState: StateStopped,
89-
}, true)
90-
require.NoError(t, err)
91-
require.NotNil(t, forked)
92-
93-
forkMem := filepath.Join(mgr.paths.InstanceSnapshotLatest(forked.Id), templateSharedMemFileName)
94-
info, err := os.Lstat(forkMem)
95-
require.NoError(t, err)
96-
assert.True(t, info.Mode().IsRegular(), "fork mem-file must be a regular file (hardlink) for firecracker fan-out")
97-
assert.True(t, sameInode(t, srcMem, forkMem), "fork mem-file must share the source's inode")
98-
}
99-
100-
// TestForkFirecrackerStandbySourceDoesNotShareMemFile guards the
101-
// non-Template carve-out: forking a plain Standby source must copy the
102-
// mem-file outright. Sharing would let a later RestoreInstance on the source
103-
// mutate the file out from under live forks.
104-
func TestForkFirecrackerStandbySourceDoesNotShareMemFile(t *testing.T) {
105-
t.Parallel()
106-
107-
mgr, _ := setupTestManager(t)
108-
ctx := context.Background()
109-
110-
sourceID := "standby-fork-fc-src"
111-
createStandbySnapshotSourceFixture(t, mgr, sourceID, "standby-fork-fc-src", hypervisor.TypeFirecracker)
112-
113-
srcSnapshotDir := mgr.paths.InstanceSnapshotLatest(sourceID)
114-
srcMem := filepath.Join(srcSnapshotDir, templateSharedMemFileName)
115-
require.NoError(t, os.WriteFile(srcMem, []byte("firecracker mem-file contents"), 0o644))
116-
snapshotConfigPath := mgr.paths.InstanceSnapshotConfig(sourceID)
117-
require.NoError(t, os.MkdirAll(filepath.Dir(snapshotConfigPath), 0o755))
118-
require.NoError(t, os.WriteFile(snapshotConfigPath, []byte(`{}`), 0o644))
119-
120-
forked, err := mgr.forkInstanceFromStoppedOrStandby(ctx, sourceID, ForkInstanceRequest{
121-
Name: "standby-fork-fc-fork",
122-
TargetState: StateStopped,
123-
}, true)
124-
require.NoError(t, err)
125-
require.NotNil(t, forked)
126-
127-
forkMem := filepath.Join(mgr.paths.InstanceSnapshotLatest(forked.Id), templateSharedMemFileName)
128-
info, err := os.Lstat(forkMem)
129-
require.NoError(t, err)
130-
require.True(t, info.Mode().IsRegular(), "standby-source fork mem-file must be a regular file copy")
131-
assert.False(t, sameInode(t, srcMem, forkMem), "standby-source fork mem-file must be a copy, not a hardlink to source")
132-
}
133-
13462
// promoteFixtureToTemplate marks the source's stored metadata as a Template
13563
// without invoking the full PromoteToTemplate lifecycle (which would require
13664
// a live VM). Test-only shortcut.

0 commit comments

Comments
 (0)