Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions .azure-pipelines/templates/Rust.Build.Job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ jobs:
version: 'ms-prod-1.93'
additionalTargets: $(triplet)
workingDirectory: $(workingDirectory)
cacheOptions:
enableTargetCache: true

steps:
- checkout: self
Expand All @@ -103,13 +105,21 @@ jobs:
profile: release
target: $(triplet)
testCondition: "eq(variables['isX64'], 'true')" # 1ES nested template takes this as a string
enableTests: $(isX64)
publishTestResults: $(isX64)
enableClippy: $(isX64)
useNextest: $(isX64)
enableTest: $(isX64)
publishTestResults: false
enableClippy: false
useNextest: false
testAllTargets: false
buildAllTargets: false

# Run clippy via plain cargo so the 1ES Setup task doesn't have to
# source-install clippy-sarif (~8 min). The toolchain ships clippy
# itself; `-D warnings` keeps the build gated.
- script: cargo clippy --locked --all-targets --all-features --target $(triplet) --release -- -D warnings
displayName: Clippy
condition: and(succeeded(), eq(variables['isX64'], 'true'))
workingDirectory: $(workingDirectory)

- task: EsrpCodeSigning@5
displayName: Code Sign
condition: and(eq(${{ parameters.isOfficialBuild }}, true), eq('${{ item.os }}', 'windows'))
Expand Down
4 changes: 2 additions & 2 deletions sdk/tests/integration/isolation-session-state-aware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import {
startSandbox,
stopSandbox,
} from '@microsoft/mxc-sdk';
import { createTempDir, probeStateAwareRuntime, safeDeprovision } from './test-helpers.js';
import { createTempDir, probeStateAwareRuntime, safeDeprovision, sandboxSkipReason } from './test-helpers.js';

const skipReason = os.platform() !== 'win32'
? 'IsolationSession is Windows-only'
: await probeStateAwareRuntime('isolation_session');
: sandboxSkipReason ?? await probeStateAwareRuntime('isolation_session');

describe('IsolationSession state-aware lifecycle E2E', { skip: skipReason }, () => {
it('runs full lifecycle: provision -> start -> exec -> stop -> deprovision', async () => {
Expand Down
3 changes: 2 additions & 1 deletion sdk/tests/unit/sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import assert from 'node:assert';
import { buildSandboxPayload, createConfigFromPolicy, spawnSandbox, spawnSandboxFromConfig } from '../../src/sandbox.js';
import { resolveExecutableAndArgs } from '../../src/helper.js';
import { ContainerConfig, SandboxPolicy, SandboxingMethod } from '../../src/types.js';
import { platformSkip } from './test-helpers.js';

describe('buildSandboxPayload', () => {
const defaultPolicy: SandboxPolicy = { version: '0.4.0-alpha' };
Expand Down Expand Up @@ -687,7 +688,7 @@ describe('Schema 0.6.0 vocabulary', () => {
});
});

describe('resolveExecutableAndArgs (containment validation)', () => {
describe('resolveExecutableAndArgs (containment validation)', { skip: platformSkip }, () => {
// Use the running node binary as a stand-in executable so the helper does
// not try to discover wxc-exec on disk. The helper does not actually exec
// anything; it just builds the path + args.
Expand Down
12 changes: 6 additions & 6 deletions sdk/tests/unit/state-aware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
} from '../../src/state-aware-helper.js';
import { MxcError } from '../../src/errors.js';
import { IsolationSessionUserConfig, SandboxId } from '../../src/state-aware-types.js';
import { fakeSpawn, testOptions } from './test-helpers.js';
import { fakeSpawn, testOptions, platformSkip } from './test-helpers.js';

describe('buildStateAwareEnvelope', () => {
it('produces a provision envelope with cross-cutting fields lifted to top-level', () => {
Expand Down Expand Up @@ -181,7 +181,7 @@ describe('parseNonExecResponse', () => {
});
});

describe('provisionSandbox', () => {
describe('provisionSandbox', { skip: platformSkip }, () => {
let activeFake: ReturnType<typeof fakeSpawn> | null = null;

beforeEach(() => { activeFake = null; });
Expand Down Expand Up @@ -236,7 +236,7 @@ describe('provisionSandbox', () => {
});
});

describe('startSandbox', () => {
describe('startSandbox', { skip: platformSkip }, () => {
afterEach(() => { _resetSpawnImpl(); });

it('infers backend from sandboxId prefix and nests configurationId under experimental', async () => {
Expand All @@ -252,7 +252,7 @@ describe('startSandbox', () => {
});
});

describe('stopSandbox', () => {
describe('stopSandbox', { skip: platformSkip }, () => {
afterEach(() => { _resetSpawnImpl(); });

it('builds a minimal stop envelope', async () => {
Expand All @@ -277,7 +277,7 @@ describe('stopSandbox', () => {
});
});

describe('deprovisionSandbox', () => {
describe('deprovisionSandbox', { skip: platformSkip }, () => {
afterEach(() => { _resetSpawnImpl(); });

it('builds a minimal deprovision envelope', async () => {
Expand All @@ -290,7 +290,7 @@ describe('deprovisionSandbox', () => {
});
});

describe('execInSandboxAsync', () => {
describe('execInSandboxAsync', { skip: platformSkip }, () => {
afterEach(() => { _resetSpawnImpl(); });

it('returns ExecResult on successful script run', async () => {
Expand Down
7 changes: 7 additions & 0 deletions sdk/tests/unit/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import { SandboxSpawnOptions } from '../../src/sandbox.js';
import { getPlatformSupport } from '../../src/platform.js';

// Skip marker for describes that hit the binary resolver: undefined when MXC
// is supported on this host, an error string when it isn't.
export const platformSkip: string | false = !getPlatformSupport().isSupported
? 'MXC not supported on this machine'
: false;

/**
* Spawn-options preset for unit tests that drive the real binary-resolver
Expand Down
10 changes: 5 additions & 5 deletions src/lxc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use wxc_common::models::{CodexRequest, ContainmentBackend, ScriptResponse};
use wxc_common::script_runner::{handle_dry_run_exit, ScriptRunner};

use lxc_common::lxc_runner::LxcScriptRunner;
#[cfg(feature = "hyperlight")]
#[cfg(all(feature = "hyperlight", target_arch = "x86_64"))]
use wxc_common::hyperlight_runner::HyperlightScriptRunner;

#[derive(Parser)]
Expand Down Expand Up @@ -113,7 +113,7 @@ fn main() {
// before config parsing so the user doesn't need a JSON file on
// disk just to install.
if cli.setup_hyperlight {
#[cfg(feature = "hyperlight")]
#[cfg(all(feature = "hyperlight", target_arch = "x86_64"))]
{
let mut logger = Logger::new(if cli.debug {
Mode::Console
Expand All @@ -131,7 +131,7 @@ fn main() {
}
}
}
#[cfg(not(feature = "hyperlight"))]
#[cfg(not(all(feature = "hyperlight", target_arch = "x86_64")))]
{
eprintln!("Error: --setup-hyperlight requires x86_64 (Hyperlight needs KVM or WHP)");
process::exit(1);
Expand Down Expand Up @@ -196,7 +196,7 @@ fn main() {
// Hyperlight is the new embedded Hyperlight+Unikraft micro-VM.
let mut runner: Box<dyn ScriptRunner> = match request.containment {
ContainmentBackend::Hyperlight => {
#[cfg(feature = "hyperlight")]
#[cfg(all(feature = "hyperlight", target_arch = "x86_64"))]
{
if !request.experimental_enabled {
eprintln!(
Expand All @@ -207,7 +207,7 @@ fn main() {
}
Box::new(HyperlightScriptRunner::new())
}
#[cfg(not(feature = "hyperlight"))]
#[cfg(not(all(feature = "hyperlight", target_arch = "x86_64")))]
{
eprintln!(
"Error: Hyperlight backend requires x86_64 (Hyperlight needs KVM or WHP)"
Expand Down
10 changes: 5 additions & 5 deletions src/wxc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use wxc_common::base_container_runner::BaseContainerRunner;
use wxc_common::config_parser::{is_base_container_version, load_mxc_request, ParseError};
use wxc_common::diagnostic::DiagnosticConfig;
use wxc_common::filesystem_bfs::FileSystemBfsManager;
#[cfg(feature = "hyperlight")]
#[cfg(all(feature = "hyperlight", target_arch = "x86_64"))]
use wxc_common::hyperlight_runner::HyperlightScriptRunner;
#[cfg(feature = "isolation_session")]
use wxc_common::isolation_session_runner::IsolationSessionRunner;
Expand Down Expand Up @@ -193,7 +193,7 @@ fn main() {
// config parsing so the user doesn't need a JSON file on disk
// just to install.
if cli.setup_hyperlight {
#[cfg(feature = "hyperlight")]
#[cfg(all(feature = "hyperlight", target_arch = "x86_64"))]
{
let mut logger = Logger::new(if cli.debug {
Mode::Console
Expand All @@ -211,7 +211,7 @@ fn main() {
}
}
}
#[cfg(not(feature = "hyperlight"))]
#[cfg(not(all(feature = "hyperlight", target_arch = "x86_64")))]
{
eprintln!("Error: --setup-hyperlight requires x86_64 (Hyperlight needs KVM or WHP)");
process::exit(1);
Expand Down Expand Up @@ -418,7 +418,7 @@ fn main() {
Box::new(NanVixScriptRunner::new())
}
ContainmentBackend::Hyperlight => {
#[cfg(feature = "hyperlight")]
#[cfg(all(feature = "hyperlight", target_arch = "x86_64"))]
{
if !request.experimental_enabled {
eprintln!(
Expand All @@ -429,7 +429,7 @@ fn main() {
}
Box::new(HyperlightScriptRunner::new())
}
#[cfg(not(feature = "hyperlight"))]
#[cfg(not(all(feature = "hyperlight", target_arch = "x86_64")))]
{
eprintln!(
"Error: Hyperlight backend requires x86_64 (Hyperlight needs KVM or WHP)"
Expand Down
4 changes: 4 additions & 0 deletions src/wxc_common/src/hyperlight_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ mod tests {
fn policy_rejects_denied_overlapping_allow() {
let mut r = runner();
let request = CodexRequest {
script_code: "print('x')".to_string(),
policy: ContainerPolicy {
readwrite_paths: vec!["/tmp/x".to_string()],
denied_paths: vec!["/tmp/x".to_string()],
Expand All @@ -706,6 +707,7 @@ mod tests {
fn policy_rejects_network_rules() {
let mut r = runner();
let request = CodexRequest {
script_code: "print('x')".to_string(),
policy: ContainerPolicy {
allowed_hosts: vec!["example.com".to_string()],
..Default::default()
Expand All @@ -722,6 +724,7 @@ mod tests {
fn policy_rejects_block_default_network() {
let mut r = runner();
let request = CodexRequest {
script_code: "print('x')".to_string(),
policy: ContainerPolicy {
default_network_policy: NetworkPolicy::Block,
..Default::default()
Expand All @@ -738,6 +741,7 @@ mod tests {
fn policy_rejects_working_directory() {
let mut r = runner();
let request = CodexRequest {
script_code: "print('x')".to_string(),
working_directory: "C:/tmp".to_string(),
..Default::default()
};
Expand Down
2 changes: 1 addition & 1 deletion src/wxc_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
pub mod config_parser;
pub mod encoding;
pub mod error;
#[cfg(feature = "hyperlight")]
#[cfg(all(feature = "hyperlight", target_arch = "x86_64"))]
pub mod hyperlight_runner;
pub mod id;
pub mod logger;
Expand Down
28 changes: 16 additions & 12 deletions src/wxc_common/src/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,20 +260,22 @@ impl Logger {
/// encountered, flush the completed line(s) to the pipe sink.
fn diag_accumulate(&mut self, text: &str) {
#[cfg(target_os = "windows")]
if self.diag_pipe.is_none() {
return;
}
#[cfg(not(target_os = "windows"))]
return;

self.diag_line_buf.push_str(text);
{
if self.diag_pipe.is_none() {
return;
}
self.diag_line_buf.push_str(text);

// Flush each complete line.
while let Some(newline_pos) = self.diag_line_buf.find('\n') {
let line = self.diag_line_buf[..newline_pos].to_string();
self.diag_line_buf.drain(..=newline_pos);
self.diag_flush_line(&line);
// Flush each complete line.
while let Some(newline_pos) = self.diag_line_buf.find('\n') {
let line = self.diag_line_buf[..newline_pos].to_string();
self.diag_line_buf.drain(..=newline_pos);
self.diag_flush_line(&line);
}
}
// Non-Windows: diagnostic pipe sink isn't implemented; accept & discard.
#[cfg(not(target_os = "windows"))]
let _ = text;
}

/// Send one complete line to the pipe sink.
Expand All @@ -290,6 +292,8 @@ impl Logger {
}
}
}
#[cfg(not(target_os = "windows"))]
let _ = line;
}

/// Flush and close diagnostic sinks.
Expand Down
Loading