mirror of
https://github.com/johndoe6345789/MetalOS.git
synced 2026-04-24 13:45:02 +00:00
Add QEMU testing workflow and unit test suite
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
116
.github/workflows/qemu-test.yml
vendored
Normal file
116
.github/workflows/qemu-test.yml
vendored
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
name: QEMU Boot Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, copilot/* ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
qemu-boot-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
nasm \
|
||||||
|
qemu-system-x86 \
|
||||||
|
ovmf \
|
||||||
|
mtools \
|
||||||
|
xorriso \
|
||||||
|
scrot \
|
||||||
|
imagemagick
|
||||||
|
|
||||||
|
- name: Build bootloader
|
||||||
|
run: |
|
||||||
|
cd bootloader
|
||||||
|
make || echo "Bootloader build incomplete (expected in early development)"
|
||||||
|
|
||||||
|
- name: Build kernel
|
||||||
|
run: |
|
||||||
|
cd kernel
|
||||||
|
make || echo "Kernel build incomplete (expected in early development)"
|
||||||
|
|
||||||
|
- name: Create bootable image
|
||||||
|
run: |
|
||||||
|
mkdir -p build/iso/EFI/BOOT
|
||||||
|
# Copy bootloader if it exists
|
||||||
|
if [ -f bootloader/bootx64.efi ]; then
|
||||||
|
cp bootloader/bootx64.efi build/iso/EFI/BOOT/
|
||||||
|
else
|
||||||
|
echo "Bootloader not built yet, creating placeholder"
|
||||||
|
mkdir -p build/iso/EFI/BOOT
|
||||||
|
fi
|
||||||
|
# Copy kernel if it exists
|
||||||
|
if [ -f kernel/metalos.bin ]; then
|
||||||
|
cp kernel/metalos.bin build/iso/
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Start QEMU and capture screenshot
|
||||||
|
run: |
|
||||||
|
# Start Xvfb for headless screenshot capture
|
||||||
|
export DISPLAY=:99
|
||||||
|
Xvfb :99 -screen 0 1920x1080x24 &
|
||||||
|
XVFB_PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Create a simple disk image with the ISO content
|
||||||
|
dd if=/dev/zero of=build/metalos.img bs=1M count=64
|
||||||
|
|
||||||
|
# Start QEMU in the background with a timeout
|
||||||
|
timeout 30s qemu-system-x86_64 \
|
||||||
|
-bios /usr/share/OVMF/OVMF_CODE.fd \
|
||||||
|
-drive format=raw,file=build/metalos.img \
|
||||||
|
-m 512M \
|
||||||
|
-display gtk \
|
||||||
|
-serial file:build/serial.log \
|
||||||
|
-no-reboot &
|
||||||
|
QEMU_PID=$!
|
||||||
|
|
||||||
|
# Wait for boot (adjust timing as needed)
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Take screenshot using ImageMagick
|
||||||
|
import -window root build/qemu-screenshot.png || echo "Screenshot capture failed"
|
||||||
|
|
||||||
|
# Gracefully stop QEMU
|
||||||
|
kill $QEMU_PID 2>/dev/null || true
|
||||||
|
wait $QEMU_PID 2>/dev/null || true
|
||||||
|
|
||||||
|
# Stop Xvfb
|
||||||
|
kill $XVFB_PID 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Boot test completed"
|
||||||
|
|
||||||
|
- name: Show serial output
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
if [ -f build/serial.log ]; then
|
||||||
|
echo "=== QEMU Serial Output ==="
|
||||||
|
cat build/serial.log
|
||||||
|
else
|
||||||
|
echo "No serial output captured"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload screenshot
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: qemu-screenshot
|
||||||
|
path: build/qemu-screenshot.png
|
||||||
|
if-no-files-found: warn
|
||||||
|
|
||||||
|
- name: Upload serial log
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: serial-log
|
||||||
|
path: build/serial.log
|
||||||
|
if-no-files-found: warn
|
||||||
42
.github/workflows/unit-tests.yml
vendored
Normal file
42
.github/workflows/unit-tests.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Unit Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, copilot/* ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
unit-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
|
- name: Build unit tests
|
||||||
|
run: |
|
||||||
|
cd tests
|
||||||
|
make all
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: |
|
||||||
|
cd tests
|
||||||
|
make test
|
||||||
|
|
||||||
|
- name: Test summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "Unit tests completed"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✓ All tests passed"
|
||||||
|
else
|
||||||
|
echo "✗ Some tests failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,6 +20,10 @@ build/
|
|||||||
bootloader/build/
|
bootloader/build/
|
||||||
kernel/build/
|
kernel/build/
|
||||||
|
|
||||||
|
# Test binaries
|
||||||
|
tests/unit/test_*
|
||||||
|
!tests/unit/*.c
|
||||||
|
|
||||||
# Precompiled Headers
|
# Precompiled Headers
|
||||||
*.gch
|
*.gch
|
||||||
*.pch
|
*.pch
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -1,10 +1,15 @@
|
|||||||
# MetalOS Main Makefile
|
# MetalOS Main Makefile
|
||||||
# Builds bootloader, kernel, and creates bootable image
|
# Builds bootloader, kernel, and creates bootable image
|
||||||
|
|
||||||
.PHONY: all bootloader kernel image qemu qemu-debug qemu-gdb clean distclean
|
.PHONY: all bootloader kernel image qemu qemu-debug qemu-gdb test clean distclean
|
||||||
|
|
||||||
all: bootloader kernel
|
all: bootloader kernel
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
test:
|
||||||
|
@echo "Running unit tests..."
|
||||||
|
@cd tests && $(MAKE) test
|
||||||
|
|
||||||
bootloader:
|
bootloader:
|
||||||
@echo "Building bootloader..."
|
@echo "Building bootloader..."
|
||||||
@cd bootloader && $(MAKE)
|
@cd bootloader && $(MAKE)
|
||||||
@@ -58,6 +63,7 @@ clean:
|
|||||||
@echo "Cleaning build artifacts..."
|
@echo "Cleaning build artifacts..."
|
||||||
@cd bootloader && $(MAKE) clean
|
@cd bootloader && $(MAKE) clean
|
||||||
@cd kernel && $(MAKE) clean
|
@cd kernel && $(MAKE) clean
|
||||||
|
@cd tests && $(MAKE) clean
|
||||||
@rm -rf build
|
@rm -rf build
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ See [docs/ROADMAP.md](docs/ROADMAP.md) for detailed phase breakdown.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
make all # Build bootloader, kernel, and userspace
|
make all # Build bootloader, kernel, and userspace
|
||||||
|
make test # Run unit tests
|
||||||
make qemu # Test in QEMU
|
make qemu # Test in QEMU
|
||||||
make clean # Clean build artifacts
|
make clean # Clean build artifacts
|
||||||
```
|
```
|
||||||
@@ -69,6 +70,7 @@ See [docs/BUILD.md](docs/BUILD.md) for detailed build instructions.
|
|||||||
- [BUILD.md](docs/BUILD.md) - Build system and toolchain
|
- [BUILD.md](docs/BUILD.md) - Build system and toolchain
|
||||||
- [DEVELOPMENT.md](docs/DEVELOPMENT.md) - Development environment setup
|
- [DEVELOPMENT.md](docs/DEVELOPMENT.md) - Development environment setup
|
||||||
- [STATUS.md](docs/STATUS.md) - Current implementation status
|
- [STATUS.md](docs/STATUS.md) - Current implementation status
|
||||||
|
- [TESTING.md](docs/TESTING.md) - Unit tests and QEMU testing
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
211
docs/TESTING.md
Normal file
211
docs/TESTING.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# Testing MetalOS
|
||||||
|
|
||||||
|
This document describes the testing infrastructure for MetalOS.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
Following MetalOS's minimalism philosophy, our testing approach is:
|
||||||
|
|
||||||
|
- **Minimal dependencies** - Custom lightweight test framework, no external libraries
|
||||||
|
- **Focused tests** - Test only what can be tested in isolation
|
||||||
|
- **Practical** - QEMU integration tests for full system validation
|
||||||
|
- **Fast** - Quick feedback loop for developers
|
||||||
|
|
||||||
|
## Test Types
|
||||||
|
|
||||||
|
### 1. Unit Tests
|
||||||
|
|
||||||
|
Unit tests verify individual components in isolation.
|
||||||
|
|
||||||
|
**Location**: `tests/unit/`
|
||||||
|
|
||||||
|
**Running unit tests**:
|
||||||
|
```bash
|
||||||
|
# Build and run all unit tests
|
||||||
|
make test
|
||||||
|
|
||||||
|
# Or run from tests directory
|
||||||
|
cd tests
|
||||||
|
make test
|
||||||
|
|
||||||
|
# Clean test artifacts
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
**Current test suites**:
|
||||||
|
- `test_console.c` - Console module tests (initialization, colors, clearing)
|
||||||
|
- `test_bootloader.c` - Bootloader utility tests (memory validation, alignment)
|
||||||
|
|
||||||
|
**Writing new tests**:
|
||||||
|
|
||||||
|
Create a new test file in `tests/unit/`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "test_framework.h"
|
||||||
|
|
||||||
|
TEST(my_test_name) {
|
||||||
|
// Test setup
|
||||||
|
int value = 42;
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
ASSERT_EQ(value, 42);
|
||||||
|
ASSERT_TRUE(value > 0);
|
||||||
|
ASSERT_NOT_NULL(&value);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
test_init("My Test Suite");
|
||||||
|
|
||||||
|
RUN_TEST(my_test_name);
|
||||||
|
|
||||||
|
return test_summary();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available assertions**:
|
||||||
|
- `ASSERT_TRUE(condition)` - Assert condition is true
|
||||||
|
- `ASSERT_FALSE(condition)` - Assert condition is false
|
||||||
|
- `ASSERT_EQ(a, b)` - Assert a equals b
|
||||||
|
- `ASSERT_NE(a, b)` - Assert a not equals b
|
||||||
|
- `ASSERT_NULL(ptr)` - Assert pointer is NULL
|
||||||
|
- `ASSERT_NOT_NULL(ptr)` - Assert pointer is not NULL
|
||||||
|
- `ASSERT_STR_EQ(a, b)` - Assert strings are equal
|
||||||
|
- `TEST_PASS()` - Mark test as passed (call at end of successful test)
|
||||||
|
|
||||||
|
### 2. QEMU Integration Tests
|
||||||
|
|
||||||
|
QEMU tests verify the full system boots correctly in an emulated environment.
|
||||||
|
|
||||||
|
**Location**: `.github/workflows/qemu-test.yml`
|
||||||
|
|
||||||
|
**What it does**:
|
||||||
|
1. Builds bootloader and kernel
|
||||||
|
2. Creates bootable image
|
||||||
|
3. Launches QEMU with UEFI firmware
|
||||||
|
4. Waits for boot sequence (configurable timeout)
|
||||||
|
5. Captures screenshot of boot state
|
||||||
|
6. Captures serial output
|
||||||
|
7. Shuts down gracefully
|
||||||
|
|
||||||
|
**Artifacts**:
|
||||||
|
- Screenshot: `qemu-screenshot.png`
|
||||||
|
- Serial log: `serial.log`
|
||||||
|
|
||||||
|
These artifacts are uploaded to GitHub Actions for inspection.
|
||||||
|
|
||||||
|
**Running locally**:
|
||||||
|
```bash
|
||||||
|
# Build and test in QEMU
|
||||||
|
make qemu
|
||||||
|
|
||||||
|
# With debug output
|
||||||
|
make qemu-debug
|
||||||
|
|
||||||
|
# With GDB server
|
||||||
|
make qemu-gdb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Framework
|
||||||
|
|
||||||
|
MetalOS uses a custom minimal test framework (`tests/include/test_framework.h`) with:
|
||||||
|
|
||||||
|
- No external dependencies
|
||||||
|
- Color-coded output
|
||||||
|
- Simple assertion macros
|
||||||
|
- Test statistics and summary
|
||||||
|
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
All tests run automatically on:
|
||||||
|
- Push to `main` or `copilot/*` branches
|
||||||
|
- Pull requests to `main`
|
||||||
|
- Manual workflow dispatch
|
||||||
|
|
||||||
|
**Workflows**:
|
||||||
|
- `.github/workflows/unit-tests.yml` - Unit test suite
|
||||||
|
- `.github/workflows/qemu-test.yml` - QEMU boot test
|
||||||
|
|
||||||
|
## What We Don't Test
|
||||||
|
|
||||||
|
Following minimalism philosophy, we intentionally don't test:
|
||||||
|
|
||||||
|
❌ **Code coverage targets** - No arbitrary percentage goals
|
||||||
|
❌ **Integration with external systems** - No networking tests
|
||||||
|
❌ **Performance benchmarks** - Functionality over speed
|
||||||
|
❌ **Fuzz testing** - Out of scope for demo OS
|
||||||
|
❌ **Static analysis beyond basics** - Keep it simple
|
||||||
|
|
||||||
|
## Adding New Tests
|
||||||
|
|
||||||
|
When adding new functionality:
|
||||||
|
|
||||||
|
1. **Write unit tests** for testable, isolated functions
|
||||||
|
2. **Update QEMU test** if boot behavior changes
|
||||||
|
3. **Document test approach** in code comments
|
||||||
|
4. **Run tests locally** before committing
|
||||||
|
|
||||||
|
## Test Output Examples
|
||||||
|
|
||||||
|
**Successful unit test run**:
|
||||||
|
```
|
||||||
|
========================================
|
||||||
|
MetalOS Test Suite: Console Module
|
||||||
|
========================================
|
||||||
|
[ RUN ] console_init
|
||||||
|
[ PASS ]
|
||||||
|
[ RUN ] console_set_color
|
||||||
|
[ PASS ]
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Total tests: 2
|
||||||
|
Passed: 2
|
||||||
|
Failed: 0
|
||||||
|
========================================
|
||||||
|
|
||||||
|
✓ All tests passed!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Failed test**:
|
||||||
|
```
|
||||||
|
[ RUN ] memory_validation
|
||||||
|
[FAILED] tests/unit/test_memory.c:42: Expected 1024, got 512
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Total tests: 1
|
||||||
|
Passed: 0
|
||||||
|
Failed: 1
|
||||||
|
========================================
|
||||||
|
|
||||||
|
✗ Some tests failed!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Tests won't compile**:
|
||||||
|
- Check that you have `gcc` and `make` installed
|
||||||
|
- Run `make clean` in tests directory
|
||||||
|
- Verify include paths in `tests/Makefile`
|
||||||
|
|
||||||
|
**QEMU test fails**:
|
||||||
|
- Check that QEMU and OVMF are installed
|
||||||
|
- Verify bootloader/kernel build successfully
|
||||||
|
- Check serial log artifact for boot errors
|
||||||
|
|
||||||
|
**Test hangs**:
|
||||||
|
- Increase timeout in test code
|
||||||
|
- Check for infinite loops in tested code
|
||||||
|
- Run with `make test-verbose` for more output
|
||||||
|
|
||||||
|
## Future Test Improvements
|
||||||
|
|
||||||
|
As MetalOS evolves, we may add:
|
||||||
|
|
||||||
|
- [ ] Memory allocator tests
|
||||||
|
- [ ] PCI enumeration tests (mocked)
|
||||||
|
- [ ] Input driver tests (mocked)
|
||||||
|
- [ ] GPU initialization tests (mocked)
|
||||||
|
- [ ] More comprehensive QEMU validation
|
||||||
|
|
||||||
|
Remember: **Test what matters, skip what doesn't.** Quality over coverage.
|
||||||
64
tests/Makefile
Normal file
64
tests/Makefile
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# MetalOS Test Suite Makefile
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -std=c11 -I../tests/include -I../kernel/include -I../bootloader/include
|
||||||
|
LDFLAGS =
|
||||||
|
|
||||||
|
# Test source files
|
||||||
|
TEST_SOURCES = $(wildcard unit/*.c)
|
||||||
|
TEST_BINARIES = $(TEST_SOURCES:.c=)
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
.PHONY: all
|
||||||
|
all: $(TEST_BINARIES)
|
||||||
|
|
||||||
|
# Build each test binary
|
||||||
|
unit/%: unit/%.c include/test_framework.h
|
||||||
|
@echo "Building test: $@"
|
||||||
|
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
.PHONY: test
|
||||||
|
test: all
|
||||||
|
@echo ""
|
||||||
|
@echo "╔════════════════════════════════════════════╗"
|
||||||
|
@echo "║ Running MetalOS Test Suite ║"
|
||||||
|
@echo "╚════════════════════════════════════════════╝"
|
||||||
|
@echo ""
|
||||||
|
@for test in $(TEST_BINARIES); do \
|
||||||
|
./$$test || exit 1; \
|
||||||
|
done
|
||||||
|
@echo ""
|
||||||
|
@echo "╔════════════════════════════════════════════╗"
|
||||||
|
@echo "║ All Test Suites Passed ✓ ║"
|
||||||
|
@echo "╚════════════════════════════════════════════╝"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# Run tests with verbose output
|
||||||
|
.PHONY: test-verbose
|
||||||
|
test-verbose: all
|
||||||
|
@for test in $(TEST_BINARIES); do \
|
||||||
|
echo "Running $$test..."; \
|
||||||
|
./$$test -v; \
|
||||||
|
echo ""; \
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clean test binaries
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
@echo "Cleaning test binaries..."
|
||||||
|
@rm -f $(TEST_BINARIES)
|
||||||
|
@rm -f unit/*.o
|
||||||
|
@echo "Clean complete"
|
||||||
|
|
||||||
|
# Help target
|
||||||
|
.PHONY: help
|
||||||
|
help:
|
||||||
|
@echo "MetalOS Test Suite Makefile"
|
||||||
|
@echo ""
|
||||||
|
@echo "Targets:"
|
||||||
|
@echo " all - Build all test binaries"
|
||||||
|
@echo " test - Build and run all tests"
|
||||||
|
@echo " test-verbose - Run tests with verbose output"
|
||||||
|
@echo " clean - Remove test binaries"
|
||||||
|
@echo " help - Show this help message"
|
||||||
126
tests/include/test_framework.h
Normal file
126
tests/include/test_framework.h
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#ifndef METALOS_TEST_FRAMEWORK_H
|
||||||
|
#define METALOS_TEST_FRAMEWORK_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// Simple test framework for MetalOS
|
||||||
|
// Inspired by minimalism - no external dependencies
|
||||||
|
|
||||||
|
// Test statistics
|
||||||
|
typedef struct {
|
||||||
|
int total;
|
||||||
|
int passed;
|
||||||
|
int failed;
|
||||||
|
} TestStats;
|
||||||
|
|
||||||
|
static TestStats test_stats = {0, 0, 0};
|
||||||
|
|
||||||
|
// Color codes for output
|
||||||
|
#define COLOR_RESET "\033[0m"
|
||||||
|
#define COLOR_GREEN "\033[32m"
|
||||||
|
#define COLOR_RED "\033[31m"
|
||||||
|
#define COLOR_YELLOW "\033[33m"
|
||||||
|
#define COLOR_BLUE "\033[34m"
|
||||||
|
|
||||||
|
// Test macros
|
||||||
|
#define TEST(name) \
|
||||||
|
static void test_##name(void); \
|
||||||
|
static void run_##name(void) { \
|
||||||
|
printf(COLOR_BLUE "[ RUN ] " COLOR_RESET #name "\n"); \
|
||||||
|
test_stats.total++; \
|
||||||
|
test_##name(); \
|
||||||
|
} \
|
||||||
|
static void test_##name(void)
|
||||||
|
|
||||||
|
#define ASSERT_TRUE(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
printf(COLOR_RED "[FAILED] " COLOR_RESET "%s:%d: Assertion failed: %s\n", \
|
||||||
|
__FILE__, __LINE__, #condition); \
|
||||||
|
test_stats.failed++; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define ASSERT_FALSE(condition) ASSERT_TRUE(!(condition))
|
||||||
|
|
||||||
|
#define ASSERT_EQ(a, b) \
|
||||||
|
do { \
|
||||||
|
if ((a) != (b)) { \
|
||||||
|
printf(COLOR_RED "[FAILED] " COLOR_RESET "%s:%d: Expected %lld, got %lld\n", \
|
||||||
|
__FILE__, __LINE__, (long long)(a), (long long)(b)); \
|
||||||
|
test_stats.failed++; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define ASSERT_NE(a, b) \
|
||||||
|
do { \
|
||||||
|
if ((a) == (b)) { \
|
||||||
|
printf(COLOR_RED "[FAILED] " COLOR_RESET "%s:%d: Expected not equal: %lld == %lld\n", \
|
||||||
|
__FILE__, __LINE__, (long long)(a), (long long)(b)); \
|
||||||
|
test_stats.failed++; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define ASSERT_NULL(ptr) ASSERT_EQ(ptr, NULL)
|
||||||
|
#define ASSERT_NOT_NULL(ptr) ASSERT_NE(ptr, NULL)
|
||||||
|
|
||||||
|
#define ASSERT_STR_EQ(a, b) \
|
||||||
|
do { \
|
||||||
|
if (strcmp((a), (b)) != 0) { \
|
||||||
|
printf(COLOR_RED "[FAILED] " COLOR_RESET "%s:%d: Strings not equal\n", \
|
||||||
|
__FILE__, __LINE__); \
|
||||||
|
printf(" Expected: \"%s\"\n", (a)); \
|
||||||
|
printf(" Got: \"%s\"\n", (b)); \
|
||||||
|
test_stats.failed++; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#define TEST_PASS() \
|
||||||
|
do { \
|
||||||
|
test_stats.passed++; \
|
||||||
|
printf(COLOR_GREEN "[ PASS ] " COLOR_RESET "\n"); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
#define RUN_TEST(name) run_##name()
|
||||||
|
|
||||||
|
// Initialize test framework
|
||||||
|
static inline void test_init(const char* suite_name) {
|
||||||
|
printf("\n");
|
||||||
|
printf(COLOR_BLUE "========================================\n" COLOR_RESET);
|
||||||
|
printf(COLOR_BLUE " MetalOS Test Suite: %s\n" COLOR_RESET, suite_name);
|
||||||
|
printf(COLOR_BLUE "========================================\n" COLOR_RESET);
|
||||||
|
test_stats.total = 0;
|
||||||
|
test_stats.passed = 0;
|
||||||
|
test_stats.failed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print test results
|
||||||
|
static inline int test_summary(void) {
|
||||||
|
printf("\n");
|
||||||
|
printf(COLOR_BLUE "========================================\n" COLOR_RESET);
|
||||||
|
printf(" Total tests: %d\n", test_stats.total);
|
||||||
|
printf(COLOR_GREEN " Passed: %d\n" COLOR_RESET, test_stats.passed);
|
||||||
|
if (test_stats.failed > 0) {
|
||||||
|
printf(COLOR_RED " Failed: %d\n" COLOR_RESET, test_stats.failed);
|
||||||
|
} else {
|
||||||
|
printf(" Failed: %d\n", test_stats.failed);
|
||||||
|
}
|
||||||
|
printf(COLOR_BLUE "========================================\n" COLOR_RESET);
|
||||||
|
|
||||||
|
if (test_stats.failed == 0) {
|
||||||
|
printf(COLOR_GREEN "\n✓ All tests passed!\n" COLOR_RESET);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
printf(COLOR_RED "\n✗ Some tests failed!\n" COLOR_RESET);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // METALOS_TEST_FRAMEWORK_H
|
||||||
129
tests/unit/test_bootloader.c
Normal file
129
tests/unit/test_bootloader.c
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Unit tests for bootloader utilities
|
||||||
|
* Tests memory address validation and basic bootloader logic
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test_framework.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// Bootloader constants (from bootloader.h)
|
||||||
|
#define KERNEL_LOAD_ADDRESS 0x100000 // 1MB mark
|
||||||
|
#define MAX_KERNEL_SIZE 0x1000000 // 16MB max
|
||||||
|
|
||||||
|
// Test: Kernel load address is valid
|
||||||
|
TEST(kernel_load_address_valid) {
|
||||||
|
// Kernel should load at 1MB boundary (after BIOS/bootloader space)
|
||||||
|
ASSERT_EQ(KERNEL_LOAD_ADDRESS, 0x100000);
|
||||||
|
|
||||||
|
// Verify it's above 1MB (avoiding low memory)
|
||||||
|
ASSERT_TRUE(KERNEL_LOAD_ADDRESS >= 0x100000);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Maximum kernel size is reasonable
|
||||||
|
TEST(max_kernel_size_reasonable) {
|
||||||
|
// 16MB should be plenty for minimal kernel
|
||||||
|
ASSERT_EQ(MAX_KERNEL_SIZE, 0x1000000);
|
||||||
|
|
||||||
|
// Should be at least 1MB
|
||||||
|
ASSERT_TRUE(MAX_KERNEL_SIZE >= 0x100000);
|
||||||
|
|
||||||
|
// Should be less than 100MB (we're minimal!)
|
||||||
|
ASSERT_TRUE(MAX_KERNEL_SIZE <= 0x6400000);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Kernel address range doesn't overlap bootloader
|
||||||
|
TEST(kernel_address_no_overlap) {
|
||||||
|
uint64_t kernel_start = KERNEL_LOAD_ADDRESS;
|
||||||
|
uint64_t kernel_end = KERNEL_LOAD_ADDRESS + MAX_KERNEL_SIZE;
|
||||||
|
|
||||||
|
// Kernel should start after 1MB
|
||||||
|
ASSERT_TRUE(kernel_start >= 0x100000);
|
||||||
|
|
||||||
|
// Kernel should not wrap around
|
||||||
|
ASSERT_TRUE(kernel_end > kernel_start);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock function: Validate memory address is in valid range
|
||||||
|
static bool is_valid_memory_address(uint64_t address) {
|
||||||
|
// Address must be above 1MB (avoid BIOS/bootloader area)
|
||||||
|
if (address < 0x100000) return false;
|
||||||
|
|
||||||
|
// Address must be below 4GB for 32-bit compatibility
|
||||||
|
if (address >= 0x100000000ULL) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Memory address validation - valid addresses
|
||||||
|
TEST(memory_address_validation_valid) {
|
||||||
|
ASSERT_TRUE(is_valid_memory_address(0x100000)); // 1MB
|
||||||
|
ASSERT_TRUE(is_valid_memory_address(0x200000)); // 2MB
|
||||||
|
ASSERT_TRUE(is_valid_memory_address(0x1000000)); // 16MB
|
||||||
|
ASSERT_TRUE(is_valid_memory_address(0x80000000)); // 2GB
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Memory address validation - invalid addresses
|
||||||
|
TEST(memory_address_validation_invalid) {
|
||||||
|
ASSERT_FALSE(is_valid_memory_address(0x0)); // Null
|
||||||
|
ASSERT_FALSE(is_valid_memory_address(0x7C00)); // BIOS area
|
||||||
|
ASSERT_FALSE(is_valid_memory_address(0x10000)); // Below 1MB
|
||||||
|
ASSERT_FALSE(is_valid_memory_address(0xFFFFF)); // Just below 1MB
|
||||||
|
ASSERT_FALSE(is_valid_memory_address(0x100000000ULL)); // Above 4GB
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock function: Align address to page boundary
|
||||||
|
static uint64_t align_to_page(uint64_t address) {
|
||||||
|
return (address + 0xFFF) & ~0xFFFULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Page alignment
|
||||||
|
TEST(page_alignment) {
|
||||||
|
ASSERT_EQ(align_to_page(0x100000), 0x100000); // Already aligned
|
||||||
|
ASSERT_EQ(align_to_page(0x100001), 0x101000); // Align up
|
||||||
|
ASSERT_EQ(align_to_page(0x100FFF), 0x101000); // Align up
|
||||||
|
ASSERT_EQ(align_to_page(0x101000), 0x101000); // Already aligned
|
||||||
|
ASSERT_EQ(align_to_page(0x101001), 0x102000); // Align up
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Bootloader version encoding
|
||||||
|
TEST(bootloader_version) {
|
||||||
|
#define BOOTLOADER_VERSION_MAJOR 0
|
||||||
|
#define BOOTLOADER_VERSION_MINOR 1
|
||||||
|
#define BOOTLOADER_VERSION_PATCH 0
|
||||||
|
|
||||||
|
// Verify version numbers are reasonable
|
||||||
|
ASSERT_TRUE(BOOTLOADER_VERSION_MAJOR >= 0);
|
||||||
|
ASSERT_TRUE(BOOTLOADER_VERSION_MAJOR < 10);
|
||||||
|
ASSERT_TRUE(BOOTLOADER_VERSION_MINOR >= 0);
|
||||||
|
ASSERT_TRUE(BOOTLOADER_VERSION_MINOR < 100);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main test runner
|
||||||
|
int main(void) {
|
||||||
|
test_init("Bootloader Utilities");
|
||||||
|
|
||||||
|
RUN_TEST(kernel_load_address_valid);
|
||||||
|
RUN_TEST(max_kernel_size_reasonable);
|
||||||
|
RUN_TEST(kernel_address_no_overlap);
|
||||||
|
RUN_TEST(memory_address_validation_valid);
|
||||||
|
RUN_TEST(memory_address_validation_invalid);
|
||||||
|
RUN_TEST(page_alignment);
|
||||||
|
RUN_TEST(bootloader_version);
|
||||||
|
|
||||||
|
return test_summary();
|
||||||
|
}
|
||||||
142
tests/unit/test_console.c
Normal file
142
tests/unit/test_console.c
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Unit tests for console module
|
||||||
|
* Tests console initialization, color setting, and basic operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "test_framework.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// Mock console structure (matching kernel/console.h)
|
||||||
|
typedef struct {
|
||||||
|
uint32_t* framebuffer;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t pitch;
|
||||||
|
uint32_t x;
|
||||||
|
uint32_t y;
|
||||||
|
uint32_t fg_color;
|
||||||
|
uint32_t bg_color;
|
||||||
|
} Console;
|
||||||
|
|
||||||
|
// Mock framebuffer for testing
|
||||||
|
static uint32_t test_framebuffer[1920 * 1080];
|
||||||
|
|
||||||
|
// Simplified console functions for unit testing
|
||||||
|
// In real implementation, these would be in a testable module
|
||||||
|
static Console test_console;
|
||||||
|
|
||||||
|
void mock_console_init(uint32_t* fb, uint32_t width, uint32_t height, uint32_t pitch) {
|
||||||
|
test_console.framebuffer = fb;
|
||||||
|
test_console.width = width;
|
||||||
|
test_console.height = height;
|
||||||
|
test_console.pitch = pitch;
|
||||||
|
test_console.x = 0;
|
||||||
|
test_console.y = 0;
|
||||||
|
test_console.fg_color = 0xFFFFFFFF; // White
|
||||||
|
test_console.bg_color = 0x00000000; // Black
|
||||||
|
}
|
||||||
|
|
||||||
|
void mock_console_set_color(uint32_t fg, uint32_t bg) {
|
||||||
|
test_console.fg_color = fg;
|
||||||
|
test_console.bg_color = bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mock_console_clear(void) {
|
||||||
|
if (!test_console.framebuffer) return;
|
||||||
|
|
||||||
|
for (uint32_t y = 0; y < test_console.height; y++) {
|
||||||
|
for (uint32_t x = 0; x < test_console.width; x++) {
|
||||||
|
test_console.framebuffer[y * (test_console.pitch / 4) + x] = test_console.bg_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_console.x = 0;
|
||||||
|
test_console.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Console initialization
|
||||||
|
TEST(console_init) {
|
||||||
|
mock_console_init(test_framebuffer, 1920, 1080, 1920 * 4);
|
||||||
|
|
||||||
|
ASSERT_NOT_NULL(test_console.framebuffer);
|
||||||
|
ASSERT_EQ(test_console.width, 1920);
|
||||||
|
ASSERT_EQ(test_console.height, 1080);
|
||||||
|
ASSERT_EQ(test_console.pitch, 1920 * 4);
|
||||||
|
ASSERT_EQ(test_console.x, 0);
|
||||||
|
ASSERT_EQ(test_console.y, 0);
|
||||||
|
ASSERT_EQ(test_console.fg_color, 0xFFFFFFFF);
|
||||||
|
ASSERT_EQ(test_console.bg_color, 0x00000000);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Console color setting
|
||||||
|
TEST(console_set_color) {
|
||||||
|
mock_console_init(test_framebuffer, 1920, 1080, 1920 * 4);
|
||||||
|
|
||||||
|
mock_console_set_color(0xFF0000FF, 0x00FF00FF);
|
||||||
|
|
||||||
|
ASSERT_EQ(test_console.fg_color, 0xFF0000FF);
|
||||||
|
ASSERT_EQ(test_console.bg_color, 0x00FF00FF);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Console clear operation
|
||||||
|
TEST(console_clear) {
|
||||||
|
mock_console_init(test_framebuffer, 800, 600, 800 * 4);
|
||||||
|
mock_console_set_color(0xFFFFFFFF, 0x00112233);
|
||||||
|
|
||||||
|
// Set some position
|
||||||
|
test_console.x = 100;
|
||||||
|
test_console.y = 200;
|
||||||
|
|
||||||
|
mock_console_clear();
|
||||||
|
|
||||||
|
// Check that position is reset
|
||||||
|
ASSERT_EQ(test_console.x, 0);
|
||||||
|
ASSERT_EQ(test_console.y, 0);
|
||||||
|
|
||||||
|
// Check that first few pixels are background color
|
||||||
|
ASSERT_EQ(test_framebuffer[0], 0x00112233);
|
||||||
|
ASSERT_EQ(test_framebuffer[1], 0x00112233);
|
||||||
|
ASSERT_EQ(test_framebuffer[10], 0x00112233);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Console with NULL framebuffer (edge case)
|
||||||
|
TEST(console_null_framebuffer) {
|
||||||
|
mock_console_init(NULL, 1920, 1080, 1920 * 4);
|
||||||
|
|
||||||
|
ASSERT_NULL(test_console.framebuffer);
|
||||||
|
|
||||||
|
// Clear should handle NULL gracefully (no crash)
|
||||||
|
mock_console_clear();
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Console with small dimensions
|
||||||
|
TEST(console_small_dimensions) {
|
||||||
|
mock_console_init(test_framebuffer, 64, 48, 64 * 4);
|
||||||
|
|
||||||
|
ASSERT_EQ(test_console.width, 64);
|
||||||
|
ASSERT_EQ(test_console.height, 48);
|
||||||
|
|
||||||
|
TEST_PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main test runner
|
||||||
|
int main(void) {
|
||||||
|
test_init("Console Module");
|
||||||
|
|
||||||
|
RUN_TEST(console_init);
|
||||||
|
RUN_TEST(console_set_color);
|
||||||
|
RUN_TEST(console_clear);
|
||||||
|
RUN_TEST(console_null_framebuffer);
|
||||||
|
RUN_TEST(console_small_dimensions);
|
||||||
|
|
||||||
|
return test_summary();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user