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
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ jobs:
- name: Type check with Mypy
run: uv run mypy .

- name: Run Tests
run: uv run pytest
- name: Run Tier 1 Unit Tests
run: make test-unit

- name: Run Tier 2 Distributed Sandbox
run: make test-dist
61 changes: 45 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@ First off, thanks for taking the time to contribute! 🎉

This project uses [uv](https://github.com/astral-sh/uv) for dependency management and Python 3.12+.

1. **Fork & Clone**
#### 1. Fork & Clone

Fork the repo and clone it locally:

```bash
git clone https://github.com/jacksonfergusondev/git-pulsar.git
cd git-pulsar
```

2. **Environment Setup**
We use `uv` to manage the virtual environment and dependencies.
#### 2. Environment Setup

We use a `Makefile` to orchestrate dev workflows. Install the environment and dependencies:

```bash
# Creates .venv and installs dependencies (including dev groups)
uv sync
make install
```

*Optional: If you use `direnv`, allow the automatically generated configuration:*
Expand All @@ -36,7 +37,8 @@ This project uses [uv](https://github.com/astral-sh/uv) for dependency managemen
direnv allow
```

3. **Install Hooks**
#### 3. Install Hooks

Set up pre-commit hooks to handle linting (Ruff) and type checking (Mypy) automatically.

```bash
Expand All @@ -45,39 +47,66 @@ This project uses [uv](https://github.com/astral-sh/uv) for dependency managemen

### Running Tests

We use `pytest` for the test suite.
We utilize a multi-tiered testing architecture to validate behavior safely.

#### Tier 1: Unit Tests

Standard unit testing for core logic.

```bash
make test-unit
```

#### Tier 2: Distributed Sandbox

Tests distributed system logic (syncing, drift detection, shadow commits) locally by simulating two isolated machines interacting with a bare remote.

```bash
uv run pytest
make test-dist
```

*Tip: Run `make test` to execute both Tier 1 and Tier 2 automatically.*

#### Tier 3: Field Testing (Linux VM)

If you are modifying OS-level daemon logic, battery polling, or doing highly destructive testing, spin up a fully isolated Ubuntu VM. Requires [Multipass](https://multipass.run/).

```bash
make test-cluster
```

This provisions a VM, mounts your local source code as read-only, and drops you into a safe `~/playground` repository. Your local Mac repository remains 100% untouched. If you edit code on your Mac, simply run `reload-pulsar` inside the VM to instantly fetch your latest changes.

### Pull Requests

1. **Create a Branch**
#### 1. Create a Branch

```bash
git checkout -b feature/my-amazing-feature
```

2. **Make Changes**
#### 2. Make Changes

Write code and add tests for your changes.

3. **Verify**
Ensure your code passes the linter and tests locally.
#### 3. Verify

Ensure your code passes the linters and automated test tiers locally.

```bash
uv run pytest
make lint
make test
```

(Pre-commit will also run `ruff` and `mypy` when you commit).
#### 4. Commit & Push

4. **Commit & Push**
Please use clear commit messages.

```bash
git commit -m "feat: add support for solar flares"
git push origin feature/my-amazing-feature
```

5. **Open a Pull Request**
#### 5. Open a Pull Request

Submit your PR against the `main` branch.
67 changes: 67 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.PHONY: help install format lint typecheck test-unit test-dist test-cluster test ci clean all

# --- ANSI Color Codes ---
BLUE=\033[1;34m
GREEN=\033[1;32m
YELLOW=\033[1;33m
NC=\033[0m # No Color

# --- Helper Macro for Clean Output ---
define PRINT_STAGE
@echo "\n$(BLUE)=== $(1) ===$(NC)"
endef

# Default target
all: format lint typecheck test

help: ## Show this help menu
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'

install: ## Install dependencies using uv
$(call PRINT_STAGE, Installing Dependencies)
uv sync --all-extras --dev

format: ## Auto-format Python code using Ruff
$(call PRINT_STAGE, Formatting Code)
uv run ruff check --fix .
uv run ruff format .

lint: ## Run linters (Ruff and Markdown)
$(call PRINT_STAGE, Running Linters)
uv run ruff check .
uv run ruff format --check .
@if command -v markdownlint >/dev/null 2>&1; then \
markdownlint "**/*.md" --ignore ".venv"; \
elif command -v npx >/dev/null 2>&1; then \
npx --yes markdownlint-cli "**/*.md" --ignore ".venv"; \
else \
echo "$(YELLOW)⚠ 'markdownlint' and 'npx' not found. Skipping markdownlint. (Requires Node.js or markdownlint-cli)$(NC)"; \
fi

typecheck: ## Run static type checking with Mypy
$(call PRINT_STAGE, Running Type Checks)
uv run mypy .

test-unit: ## Run Tier 1 unit tests
$(call PRINT_STAGE, Running Tier 1: Unit Tests)
uv run pytest

test-dist: ## Run Tier 2 distributed sandbox tests
$(call PRINT_STAGE, Running Tier 2: Distributed Sandbox)
bash scripts/test_distributed.sh

test-cluster: ## Spawn Tier 3 Multipass VM cluster for OS field testing
$(call PRINT_STAGE, Provisioning Tier 3: Field Test Cluster)
bash scripts/spawn_cluster.sh

test: test-unit test-dist ## Run all automated testing tiers (1 & 2)
@echo "\n$(GREEN)✔ All automated test tiers passed successfully.$(NC)"

ci: install lint typecheck test ## Run the exact pipeline executed by GitHub Actions
@echo "\n$(GREEN)✔ Local CI pipeline completed successfully. Clear to push!$(NC)"

clean: ## Remove cache directories and test artifacts
$(call PRINT_STAGE, Cleaning Workspace)
rm -rf .pytest_cache .mypy_cache .ruff_cache
find . -type d -name "__pycache__" -exec rm -rf {} +
@echo "$(GREEN)✔ Environment cleaned.$(NC)"
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ In a distributed environment (Laptop ↔ Desktop), state drift is inevitable.

---

## 🛡️ Verification & Reliability

Because Git Pulsar operates asynchronously on the user's active working directory, a single race condition or unhandled edge case could corrupt a repository. Testing isn't just about coverage; it is about guaranteeing **Data Integrity** and **Zero-Interference**.

The project utilizes a strict, multi-tiered testing architecture orchestrated via CI/CD:

1. **Tier 1: Property Fuzzing & Plumbing (Unit)**
- Uses `pytest` and `Hypothesis` to fuzz critical registry paths and file manipulation logic.
- Deeply mocks the OS layer and strictly asserts that the daemon only invokes non-destructive Git plumbing commands (`write-tree`, `commit-tree`), verifying it never touches the user's porcelain state.
2. **Tier 2: Distributed Sandbox (Integration)**
- Simulates a multi-node distributed environment entirely locally.
- Exploits the `XDG_STATE_HOME` environment variable to spin up multiple isolated daemon instances that push and pull to a local bare remote, validating drift detection, session handoffs, and concurrent conflict resolution.
3. **Tier 3: Chaos Engineering (OS-Level Field Tests)**
- Fully automates the provisioning of ephemeral Ubuntu virtual machines (via Canonical's Multipass) to test OS-level integrations.
- Validates `systemd` user timers, `sysfs` battery polling limits, and allows for safe, destructive testing on a live Linux filesystem without risking the host machine's source code.

---

## 📦 Installation

### macOS
Expand Down
85 changes: 85 additions & 0 deletions scripts/spawn_cluster.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -e

BLUE='\033[1;34m'
GREEN='\033[1;32m'
RED='\033[1;31m'
NC='\033[0m'

NODE_NAME="pulsar-field-node"

# Dependency check
if ! command -v multipass &> /dev/null; then
echo -e "${RED}❌ Multipass is not installed.${NC}"
echo "Please install it via: brew install --cask multipass"
exit 1
fi

echo -e "${BLUE}=== Provisioning Tier 3 Linux Field Node ===${NC}"

# Clean up existing instance if it exists
if multipass info "$NODE_NAME" &> /dev/null; then
echo -e "ℹ Destroying existing node '$NODE_NAME'..."
multipass delete "$NODE_NAME"
multipass purge
fi

echo -e "ℹ Launching fresh Ubuntu instance (cached images boot in ~5s)..."
multipass launch --name "$NODE_NAME"

echo -e "ℹ Waiting for VM networking and SSHFS to stabilize..."
multipass exec "$NODE_NAME" -- bash -c "while ! id -g >/dev/null 2>&1; do sleep 1; done"

echo -e "ℹ Preparing mount point..."
multipass exec "$NODE_NAME" -- sudo mkdir -p /mnt/pulsar-source
multipass exec "$NODE_NAME" -- sudo chown ubuntu:ubuntu /mnt/pulsar-source

echo -e "ℹ Mounting git-pulsar source code (Retrying until VM is ready)..."
MAX_RETRIES=5
RETRY_COUNT=0
MOUNT_SUCCESS=false

while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
if multipass mount "$(pwd)" "$NODE_NAME":/mnt/pulsar-source &> /dev/null; then
MOUNT_SUCCESS=true
break
fi
echo -e " [Wait] SSHFS not ready, retrying in 3s... ($((RETRY_COUNT+1))/$MAX_RETRIES)"
sleep 3
RETRY_COUNT=$((RETRY_COUNT+1))
done

if [ "$MOUNT_SUCCESS" = false ]; then
echo -e "${RED}❌ Fatal: Could not mount directory after $MAX_RETRIES attempts.${NC}"
exit 1
fi

echo -e "ℹ Bootstrapping Python environment (uv)..."
multipass exec "$NODE_NAME" -- bash -c "curl -LsSf https://astral.sh/uv/install.sh | sh"

echo -e "ℹ Configuring global Git identity for shadow commits..."
multipass exec "$NODE_NAME" -- git config --global user.name "Pulsar Field Tester"
multipass exec "$NODE_NAME" -- git config --global user.email "test@pulsar.dev"
multipass exec "$NODE_NAME" -- git config --global pull.rebase false

echo -e "ℹ Creating isolated playground repository..."
multipass exec "$NODE_NAME" -- bash -c "mkdir -p ~/playground && cd ~/playground && git init && echo '# Playground' > README.md && git add README.md && git commit -m 'Initial commit' && git branch -M main"

echo -e "ℹ Installing git-pulsar into the VM..."
# Using 'uv tool' installs the package globally without messing with project .venvs
multipass exec "$NODE_NAME" -- bash -c "source ~/.local/bin/env && uv tool install /mnt/pulsar-source"

echo -e "ℹ Setting up auto-activation and aliases..."
multipass exec "$NODE_NAME" -- bash -c 'cat <<EOF >> ~/.bashrc
source ~/.local/bin/env
cd ~/playground
alias reload-pulsar="uv tool install --force /mnt/pulsar-source"

echo -e "\n\033[1;32m✔ Git Pulsar Field Node Active.\033[0m"
echo -e "You are in an isolated sandbox (~\033[1;34m/playground\033[0m). Your Mac repo is 100% safe."
echo -e "Run \033[1;36mreload-pulsar\033[0m to fetch the latest code if you edit files on your Mac."
EOF'

echo -e "\n${GREEN}✔ Cluster Provisioned Successfully.${NC}"
echo -e "\nTo begin field testing, run:"
echo -e " ${BLUE}multipass shell $NODE_NAME${NC}"
Loading