Skip to content

Commit c35f103

Browse files
author
Shreyansh Sancheti
committed
hcs: add driver interface for HCS system and process operations
The HCS layer (internal/hcs/system.go, process.go) calls vmcompute.dll directly, making it impossible to unit test the System/Process logic or swap the underlying DLL for the upcoming V2 migration (computecore.dll). This change introduces an internal hcsDriver interface that wraps all vmcompute system and process API calls. The System struct now holds a driver field (defaulting to the vmcomputeDriver, which delegates to the existing vmcompute package). Process inherits the driver from its parent System. No behavioral change — the vmcomputeDriver methods are one-liner delegations to the same vmcompute functions previously called directly. This enables writing unit tests against the HCS layer without admin privileges or a live HCS service, by injecting a mock driver. 17 tests are included covering: handle guards, error swallowing, async pending paths, system crash and service disconnect during operations, the waitBackground exit classification, multi-goroutine Wait fan-out, and late callback safety after unregistration. Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>
1 parent 5a0252a commit c35f103

7 files changed

Lines changed: 1241 additions & 29 deletions

File tree

internal/hcs/export_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//go:build windows
2+
3+
package hcs
4+
5+
import (
6+
"context"
7+
8+
"github.com/Microsoft/hcsshim/internal/vmcompute"
9+
)
10+
11+
// SetDriverForTest replaces the SystemDriver on a System instance.
12+
// This is intended for use in tests to inject a mock driver.
13+
func SetDriverForTest(s *System, d hcsDriver) {
14+
s.driver = d
15+
}
16+
17+
// FireNotificationForTest simulates an HCS notification arriving on the
18+
// notification channel for the given callback number. This allows tests
19+
// to exercise the async completion paths without a real HCS system.
20+
func FireNotificationForTest(callbackNumber uintptr, notification hcsNotification, result error) {
21+
callbackMapLock.RLock()
22+
ctx := callbackMap[callbackNumber]
23+
callbackMapLock.RUnlock()
24+
if ctx == nil {
25+
return
26+
}
27+
if ch, ok := ctx.channels[notification]; ok {
28+
ch <- result
29+
}
30+
}
31+
32+
// GetCallbackNumberForTest returns the callback number of a System.
33+
func GetCallbackNumberForTest(s *System) uintptr {
34+
return s.callbackNumber
35+
}
36+
37+
// NewTestSystem creates a System with the given ID for testing.
38+
// The handle is zero (closed state), useful for testing error guards.
39+
func NewTestSystem(id string) *System {
40+
return newSystem(id)
41+
}
42+
43+
// NewTestSystemWithDriver creates a System with a custom driver and a
44+
// non-zero handle for testing operations that require an open handle.
45+
func NewTestSystemWithDriver(id string, driver hcsDriver, handle uintptr) *System {
46+
s := newSystem(id)
47+
s.driver = driver
48+
s.handle = vmcompute.HcsSystem(handle)
49+
return s
50+
}
51+
52+
// RegisterCallbackForTest calls registerCallback on a test system so the
53+
// notification channels are set up. This is needed for testing async paths
54+
// (operations that return ErrVmcomputeOperationPending and wait on channels).
55+
func RegisterCallbackForTest(s *System) error {
56+
return s.registerCallback(context.Background())
57+
}
58+
59+
// StartWaitBackgroundForTest launches the waitBackground goroutine for a test
60+
// system. This is normally done by CreateComputeSystem/OpenComputeSystem.
61+
func StartWaitBackgroundForTest(s *System) {
62+
go s.waitBackground()
63+
}
64+
65+
// WaitErrorForTest returns the waitError field of a System.
66+
func WaitErrorForTest(s *System) error {
67+
return s.waitError
68+
}
69+
70+
// ExitErrorForTest returns the exitError field of a System.
71+
func ExitErrorForTest(s *System) error {
72+
return s.exitError
73+
}
74+
75+
// UnregisterCallbackForTest calls unregisterCallback on a test system.
76+
func UnregisterCallbackForTest(s *System) error {
77+
return s.unregisterCallback(context.Background())
78+
}
79+
80+
// CallbackExistsForTest checks if a callbackNumber is still in the global callbackMap.
81+
func CallbackExistsForTest(callbackNumber uintptr) bool {
82+
callbackMapLock.RLock()
83+
defer callbackMapLock.RUnlock()
84+
_, exists := callbackMap[callbackNumber]
85+
return exists
86+
}
87+
88+
// Expose notification values for test assertions.
89+
var (
90+
HcsNotificationSystemExited = hcsNotificationSystemExited
91+
HcsNotificationSystemCreateCompleted = hcsNotificationSystemCreateCompleted
92+
HcsNotificationSystemStartCompleted = hcsNotificationSystemStartCompleted
93+
HcsNotificationSystemPauseCompleted = hcsNotificationSystemPauseCompleted
94+
HcsNotificationSystemResumeCompleted = hcsNotificationSystemResumeCompleted
95+
HcsNotificationSystemSaveCompleted = hcsNotificationSystemSaveCompleted
96+
HcsNotificationServiceDisconnect = hcsNotificationServiceDisconnect
97+
)

0 commit comments

Comments
 (0)