Skip to content

service: add unit tests for LCOW v2 shim service layer#2649

Merged
shreyanshjain7174 merged 5 commits into
microsoft:mainfrom
shreyanshjain7174:service/unit-tests
Jun 30, 2026
Merged

service: add unit tests for LCOW v2 shim service layer#2649
shreyanshjain7174 merged 5 commits into
microsoft:mainfrom
shreyanshjain7174:service/unit-tests

Conversation

@shreyanshjain7174

@shreyanshjain7174 shreyanshjain7174 commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Adds unit tests for the LCOW v2 shim service layer. The service orchestrates sandbox lifecycle (create / start / stop / wait / shutdown / status / metrics) by delegating to vm.Controller; without these tests every code change had to be validated against a live HCS environment.

Design

The Service struct holds a single vmController interface field (defined in types.go). Production passes *vm.Controller; tests pass a generated mock. The interface is a superset that includes the methods pod.New needs (Guest(), SCSIController(), Plan9Controller(), NetworkController() etc.) so the same field satisfies both Service's direct calls and pod.New's narrower interface via Go structural typing - no concrete-pointer leakage. This mirrors the merged internal/controller/pod pattern.

Coverage

49 tests across three files matching the production layout:

  • service_sandbox_internal_test.go (23): duplicate sandbox rejection, missing config.json, VM create / start failure, sandbox-id mismatch guards, stop success / failure / idempotency, wait clean / error / wait-failure / exit-status-failure exits, status mapping for every state, ping not-implemented, shutdown idempotency / running-VM termination / terminate-error swallowed, metrics success and stats failure.
  • service_shimdiag_internal_test.go (10): pid passthrough, exec-in-host success / failure, tasks / share / stacks state guards, share missing host-path validation, stacks dump-failure and success.
  • service_task_internal_test.go (16): consolidated state-guard and unknown-container guard tables, not-implemented surfaces, shutdown no-op, update validation (nil resources rejected, dispatch by resource type, per-resource failure surfaces), enrichNotFoundError pass-through and ErrNotFound preservation.

Out of scope

  • The full CreateSandbox -> BuildSandboxConfig -> CreateVM path is exercised by internal/builder/vm/lcow/ tests and the test/parity/vm/ parity suite; the service-level surface is the orchestration logic only.
  • The confidential StartSandbox path reads boot files from disk; belongs in functional tagged tests.

Mocks

mocks/mock_service.go is checked in. Standardising mock generation (directive in test file + CI drift validation) is tracked in #2707.

Comment thread cmd/containerd-shim-lcow-v2/service/service.go Outdated
Comment thread cmd/containerd-shim-lcow-v2/service/service.go Outdated
Comment thread cmd/containerd-shim-lcow-v2/service/types.go Outdated
Comment thread cmd/containerd-shim-lcow-v2/service/service.go
Comment thread cmd/containerd-shim-lcow-v2/service/service_sandbox_internal_test.go Outdated
Comment thread cmd/containerd-shim-lcow-v2/service/service_sandbox_internal_test.go Outdated
Comment thread cmd/containerd-shim-lcow-v2/service/service_shimdiag_internal_test.go Outdated
@shreyanshjain7174 shreyanshjain7174 requested review from jterry75 and removed request for jterry75 June 30, 2026 06:32
Shreyansh Sancheti added 3 commits June 30, 2026 12:21
Defines a narrow vmController interface in the service package and threads
it through Service in place of the concrete *vm.Controller field. Production
passes vm.New(); tests pass a generated gomock. The interface includes the
methods pod.New requires (Guest, SCSIController, VPCIController,
Plan9Controller, NetworkController) so the same field satisfies both
Service's direct calls and pod.New's narrower interface via Go structural
typing.

Tests cover the production failure paths through the service RPC surface:

Sandbox API (23 tests):
- duplicate Create rejection, missing config.json, VM create / start failure
- sandbox-id mismatch guards
- stop success / failure / idempotency
- wait clean / error / wait-failure / exit-status-failure exits
- status mapping for every state, ping not-implemented
- shutdown idempotency, running-VM termination, terminate-error swallowed
- metrics success and stats failure

Shimdiag API (10 tests):
- pid passthrough, exec-in-host success / failure
- tasks / share / stacks state guards
- share missing host-path validation
- stacks dump-failure and success

Task API (16 tests):
- consolidated state-guard and unknown-container guard tables
- not-implemented surfaces, shutdown no-op
- update validation: nil resources rejected, dispatch by resource type,
  per-resource failure surfaces
- enrichNotFoundError pass-through and ErrNotFound preservation

Mocks committed at mocks/mock_service.go with the standard build tag
(windows && lcow). Standardising mock generation across controllers is
tracked in microsoft#2707.

Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>
…rt grouping

Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>
…empDir() for missing-path test

Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>

@rawahars rawahars left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

service_task_internal_test.go does not include tests for-

createInternal

startInternal

stateInternal

deleteInternal

pidsInternal

killInternal

execInternal

resizePtyInternal

closeIOInternal

waitInternal

statsInternal

}
}

// ─── diagShareInternal tests ──────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a success case as well. In the mock request, ensure that the params received by the controller are same as what were set in the test.

}
}

// ─── diagTasksInternal tests ──────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a success case as well. In the mock request, ensure that the params received by the controller are same as what were set in the test.

svc, mockCtrl := newTestService(t)

req := &shimdiag.ExecProcessRequest{Args: []string{"/bin/ls"}, Workdir: "/"}
mockCtrl.EXPECT().ExecIntoHost(gomock.Any(), req).Return(42, nil)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you verify in this mock request that the params received by the controller are same as the ones set in test?

}
}

// ─── createSandboxInternal tests ──────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a success case as well. In the mock request, ensure that the params received by the controller are same as what were set in the test.

// over the concrete VM controller, so that the vmController interface only
// needs to declare the methods the service itself calls; the VM guest and
// device accessors that pod.New consumes are not surfaced on that interface.
newPod func(podID, networkNamespaceID string) *pod.Controller

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't introduce new abstractions in service struct. Please use pod.New itself.


// NewService creates a new instance of the Service with the shared state.
func NewService(ctx context.Context, eventsPublisher shim.Publisher, sd shutdown.Service) *Service {
vmc := vm.New()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why move this outside the struct?

Shreyansh Sancheti added 2 commits June 30, 2026 20:04
Addresses review feedback (rawahars): createSandboxInternal had only error-path coverage (duplicate, missing config.json, CreateVM failure). Adds a happy-path test that asserts the CreateOptions forwarded to vmController.CreateVM are derived from the request (ID, Owner, BundlePath, parsed SandboxSpec) instead of matching gomock.Any(), and that sandboxID is recorded only after CreateVM succeeds.

Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>
Introduce a consumer-side containerController interface that *linuxcontainer.Controller satisfies as-is: the child getters keep returning the concrete *process.Controller, so the production controller needs no adapter or wrapper. Make getContainerController an injectable package-level function variable so unit tests can substitute a mock container controller without building a real pod/container chain.

Add success-path tests for the one-hop delegation methods (Pids, Stats, DeleteProcess exec, Kill single-container, Update container, Start init), asserting the forwarded arguments and response mapping. Methods that take a second hop through the process controller are intentionally left to functional coverage.

Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>
@shreyanshjain7174

Copy link
Copy Markdown
Contributor Author

Added success-path unit tests for the one-hop container-delegation methods (Pids, Stats, DeleteProcess exec, Kill single-container, Update container, Start init) via a small consumer-side containerController interface that *linuxcontainer.Controller satisfies as-is — no adapter or production signature change — with getContainerController made an injectable package-level function variable for the mock.

On why the rest don't get success-path unit tests: state, exec, resizePty, closeIO, wait, start (exec path), create, and diagTasks make a second hop through the process controller via ctrCtrl.GetProcess() → *process.Controller. Mocking that hop would force GetProcess to return an interface, i.e. an adapter on the production controller, so they stay on functional coverage.

@shreyanshjain7174 shreyanshjain7174 merged commit c697858 into microsoft:main Jun 30, 2026
32 of 33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants