WSL-UI 구축: Mock Mode와 Fake Distros
I’m sorry, but I can’t access external links to retrieve the article’s text. Could you please paste the content you’d like translated here? Once you provide the text, I’ll translate it into Korean while preserving the formatting and source link as you requested.
Architectural Decision: Mock Mode for WSL‑UI
WSL‑UI에서 처음으로 내린 설계 결정 중 하나는 완전한 mock mode를 구축하는 것이었습니다.
이는 자동화 테스트를 위한 것뿐만 아니라—그것도 중요하지만—일상적인 개발에도 활용됩니다.
왜?
- 디버깅 중에 실수로 실제 WSL 배포판을 삭제하고 싶지 않았습니다.
- 실제 배포판으로는 재현하기 어려운 시나리오(예: 네트워크 타임아웃, 손상된 레지스트리 항목)를 테스트해야 했습니다.
핵심 통찰은 Domain‑Driven Design의 Anti‑Corruption Layer 패턴에서 얻었습니다.
명령 핸들러에서 wsl.exe를 직접 호출하는 대신, 런타임에 교체할 수 있는 추상화 레이어를 만들었습니다.
Trait Definitions (Rust)
pub trait WslCommandExecutor: Send + Sync {
fn list_distributions(&self) -> Result<(), WslError>;
fn start_distribution(&self, name: &str) -> Result<(), WslError>;
fn stop_distribution(&self, name: &str) -> Result<(), WslError>;
fn terminate_distribution(&self, name: &str) -> Result<(), WslError>;
fn import_distribution(
&self,
name: &str,
path: &Path,
location: &Path,
) -> Result<(), WslError>;
// …more operations
}
pub trait ResourceMonitor: Send + Sync {
fn get_memory_usage(&self, name: &str) -> Result<u64, WslError>;
fn get_cpu_percentage(&self, name: &str) -> Result<f64, WslError>;
fn get_vhdx_size(&self, name: &str) -> Result<u64, WslError>;
fn get_registry_info(&self, name: &str) -> Result<RegistryInfo, WslError>;
}
pub trait TerminalExecutor: Send + Sync {
fn open_terminal(&self, name: &str) -> Result<(), WslError>;
fn execute_command(&self, name: &str, command: &str) -> Result<CommandOutput, WslError>;
}
- Real implementations는
wsl.exe를 호출하고, Windows 레지스트리를 읽으며, Windows Terminal과 상호 작용합니다. - Mock implementations는 내부 상태를 유지하고 결정적이며 제어 가능한 응답을 반환합니다.
Default Mock Distributions
mock mode가 활성화되면 애플리케이션은 일곱 개의 가짜 배포판으로 시작합니다:
| Name | Version | State | Install Source |
|---|---|---|---|
| Ubuntu | WSL2 | Running | Microsoft Store |
| Debian | WSL2 | Stopped | LXC Container |
| Alpine | WSL2 | Stopped | Container Import |
| Ubuntu‑22.04 | WSL2 | Running | Download |
| Fedora | WSL2 | Running | Download |
| Arch | WSL2 | Stopped | Download |
| Ubuntu‑legacy | WSL1 | Stopped | Legacy |
각 배포판은 시뮬레이션된 리소스 사용량을 가집니다:
let (mock_memory, mock_cpu) = match distro {
"Ubuntu" => (512_000_000, 2.5), // ~512 MiB, 2.5 % CPU
"Ubuntu-22.04" => (384_000_000, 1.8), // ~384 MiB, 1.8 % CPU
"Debian" => (256_000_000, 0.5), // ~256 MiB, 0.5 % CPU
"Alpine" => (64_000_000, 0.2), // ~64 MiB, 0.2 % CPU
"Fedora" => (196_000_000, 1.2), // ~196 MiB, 1.2 % CPU
_ => (128_000_000, 0.3), // Default values
};
- 레지스트리 항목은 현실적인 GUID, 경로, 패키지 정보를 포함합니다.
- 디스크 크기는 500 MiB부터 8 GiB까지 다양합니다.
- mock은 디스크 마운트 기능을 위해 물리 디스크(500 GB SSD와 1 TB HDD 및 파티션)까지 시뮬레이션합니다.
mock 상태는 동적입니다:
- 배포판을 시작하면 상태가
Running으로 바뀝니다. - 정지하면
Stopped로 돌아갑니다. - 새 배포판을 만들면 리스트에 추가되고, 삭제하면 리스트에서 제거됩니다.
Mock Executor Skeleton
pub struct MockWslExecutor {
distributions: Arc<Mutex<HashMap<String, MockDistribution>>>,
default_distribution: Arc<Mutex<Option<String>>>,
error_simulation: Arc<Mutex<Option<ErrorSimulation>>>,
}
impl MockWslExecutor {
pub fn new() -> Self {
let mut distros = HashMap::new();
// Initialise with the 7 default distributions
distros.insert(
"Ubuntu".to_string(),
M```
> **Source:**
```rust
ockDistribution {
guid: generate_guid(),
name: "Ubuntu".to_string(),
state: DistroState::Running,
version: WslVersion::Wsl2,
// …
},
);
// …more distros
Self {
distributions: Arc::new(Mutex::new(distros)),
default_distribution: Arc::new(Mutex::new(Some("Ubuntu".to_string()))),
error_simulation: Arc::new(Mutex::new(None)),
}
}
}
Arc<Mutex<…>> 패턴은 스레드 안전성을 보장합니다 – Tauri 명령은 여러 스레드에서 호출될 수 있으며, 모의 상태는 일관성을 유지해야 합니다.
Simulating Errors on Demand
행복한 경로( happy‑path ) 흐름을 테스트하는 것은 간단하지만, 오류 처리를 테스트하려면 오류를 강제로 발생시킬 수 있어야 합니다.
pub struct ErrorSimulation {
pub operation: String,
pub error_type: ErrorType,
pub delay_ms: u64,
}
pub enum ErrorType {
Timeout,
CommandFailed,
NotFound,
Cancelled,
}
프론트엔드(또는 E2E 테스트)에서 실패를 예약할 수 있습니다:
await invoke('set_mock_error', {
operation: 'start_distribution',
errorType: 'timeout',
delayMs: 5000,
});
// 다음 start_distribution 호출은 5 초 후에 타임아웃됩니다
await invoke('start_distribution', { name: 'Ubuntu' });
이를 통해 확인할 수 있는 항목:
- 느린 작업 중 진행 대화상자
- 오류 알림 UI
- 재시도 로직
- 우아한 디그레이데이션
Enabling Mock Mode
모의 모드는 환경 변수를 통해 토글됩니다:
# 이 중 하나를 설정하면 모의 모드가 활성화됩니다
WSL_MOCK=1
WSL_UI_MOCK_MODE=1
앱이 시작될 때 플래그를 확인하고 적절한 구현을 연결합니다:
fn init_executors() {
if crate::utils::is_mock_mode() {
// Mock implementations
let wsl_mock = Arc::new(MockWslExecutor::new());
WSL_EXECUTOR.get_or_init(|| wsl_mock.clone());
let resource_mock = MockResourceMonitor::with_wsl_mock(wsl_mock.clone());
RESOURCE_MONITOR.get_or_init(|| Arc::new(resource_mock));
let terminal_mock = MockTerminalExecutor::new();
TERMINAL_EXECUTOR.get_or_init(|| Arc::new(terminal_mock));
} else {
// Real implementations
WSL_EXECUTOR.get_or_init(|| Arc::new(RealWslExecutor));
RESOURCE_MONITOR.get_or_init(|| Arc::new(RealResourceMonitor));
TERMINAL_EXECUTOR.get_or_init(|| Arc::new(RealTerminalExecutor));
}
}
Summary
- Mock mode는 실제 WSL 설치를 보호하면서 풍부하고 제어 가능한 테스트 환경을 제공합니다.
- Anti‑Corruption Layer(트레이트 + 구체 구현) 덕분에 실제 코드와 모의 코드를 쉽게 전환할 수 있습니다.
- 상태 기반 모의, 오류 시뮬레이션, 그리고 환경 변수 기반 활성화를 통해 개발, 수동 테스트, CI 파이프라인이 더 빠르고 안전하며 신뢰할 수 있게 됩니다.
Frontend Mock Support
모의 모드는 백엔드에만 국한되지 않습니다. 프론트엔드는 E2E 테스트 중 window 객체에 Zustand 스토어를 노출합니다:
// 개발/테스트 모드에서
if (import.meta.env.DEV || import.meta.env.MODE === 'test') {
(window as any).__distroStore = useDistroStore;
(window as any).__notificationStore = useNotificationStore;
}
이를 통해 E2E 테스트가 애플리케이션 상태를 직접 검사하고 조작할 수 있습니다:
// WebdriverIO 테스트에서
const store = await browser.execute(() => window.__distroStore.getState());
expect(store.distributions).toHaveLength(7);
// 테스트 간 초기 상태로 리셋
await browser.execute(() => window.__distroStore.getState().reset());
Was the effort worth it?
절대 그렇다. 모의 모드를 구축하는 데 상당한 노력이 들어갔으며—전체 프로젝트 시간의 약 15‑20 % 정도—하지만 여러 면에서 큰 효과를 보았습니다:
- 빠른 개발 – 실제 WSL 작업을 기다릴 필요가 없으며, 배포판 시작이 수초가 아니라 밀리초 단위로 이루어집니다.
- 안전한 실험 – 파괴적인 작업(삭제, 포맷 등)을 위험 없이 테스트할 수 있습니다.
- 재현 가능한 테스트 – E2E 테스트가 실제 환경에 의존하지 않고 일관된 결과를 얻을 수 있습니다.
tests run against an identical initial state every time.
- Offline development – No need for actual WSL distributions to be installed.
- Edge‑case coverage – Easy to test scenarios like “what if the user has 50 distributions?”.
- CI/CD friendly – Tests run in GitHub Actions on a clean Windows runner with no WSL setup required.
테스트는 매번 동일한 초기 상태에서 실행됩니다.
- 오프라인 개발 – 실제 WSL 배포판을 설치할 필요가 없습니다.
- 엣지 케이스 커버리지 – “사용자가 50개의 배포판을 가지고 있다면?”과 같은 시나리오를 쉽게 테스트할 수 있습니다.
- CI/CD 친화적 – 테스트는 WSL 설정이 필요 없는 깨끗한 Windows 러너에서 GitHub Actions로 실행됩니다.
교훈
다시 한다면, 모의 모드를 더 일찍 시작했을 것입니다. 추상화 계층은 테스트뿐만 아니라 개발 전반에 걸쳐 큰 도움이 됩니다.
다르게 할 점
- 보다 현실적인 타이밍 – 모의가 너무 빠릅니다. 실제 WSL 작업은 눈에 띄는 지연이 있습니다. 구성 가능한 지연을 추가하면 개발 경험이 더 현실적이 됩니다.
- 영속성 옵션 – 현재 모의는 앱 재시작 시 초기화됩니다. 모의 상태를 파일에 저장하는 옵션은 장시간 테스트 세션에 유용합니다.
- 퍼즈 테스트 – 엣지 케이스를 찾기 위한 무작위 작업 순서. 인프라는 이미 준비돼 있으니, 테스트만 작성하면 됩니다.
다음은?
이 시리즈의 다음 게시물에서는 배포판 이름 바꾸기의 복잡한 세부 사항을 살펴볼 예정이며, 이를 제대로 작동시키기 위해 필요한 Windows 레지스트리 변경 사항도 포함됩니다.
WSL‑UI 받기
WSL‑UI는 오픈 소스이며 다음에서 이용할 수 있습니다:
- Microsoft Store – “WSL UI”를 검색하거나 스토어 목록을 방문하세요.
- Direct Download – 공식 웹사이트.
- GitHub – https://github.com/octasoft-ltd/wsl-ui