Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
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
4 changes: 2 additions & 2 deletions docs/source/getting-started/setup-local-exporter.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ from jumpstarter_testing.pytest import JumpstarterTest

class MyTest(JumpstarterTest):
def test_power_on(self, client):
assert client.power.on() == "ok"
client.power.on()

def test_power_off(self, client):
assert client.power.off() == "ok"
client.power.off()
```

```shell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ def test_drivers_composite():
},
)
) as client:
assert client.power0.on() == "ok"
assert client.composite1.power1.on() == "ok"
client.power0.on()
client.composite1.power1.on()
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ def close(self):
self.off()

@export
def on(self):
return self.control("on")
def on(self) -> None:
self.control("on")

@export
def off(self):
return self.control("off")
def off(self) -> None:
self.control("off")

@export
async def read(self) -> AsyncGenerator[PowerReading, None]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@


class PowerClient(DriverClient):
def on(self) -> str:
return self.call("on")
def on(self) -> None:
self.call("on")

def off(self) -> str:
return self.call("off")
def off(self) -> None:
self.call("off")

def read(self) -> Generator[PowerReading, None, None]:
for v in self.streamingcall("read"):
Expand All @@ -26,11 +26,11 @@ def base():
@base.command()
def on():
"""Power on"""
click.echo(self.on())
self.on()

@base.command()
def off():
"""Power off"""
click.echo(self.off())
self.off()

return base
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

def test_client_mock_power():
with serve(MockPower()) as client:
assert client.on() == "ok"
assert client.off() == "ok"
client.on()
client.off()

assert list(client.read()) == [
PowerReading(voltage=0.0, current=0.0),
Expand All @@ -16,8 +16,8 @@ def test_client_mock_power():

def test_client_sync_mock_power():
with serve(SyncMockPower()) as client:
assert client.on() == "ok"
assert client.off() == "ok"
client.on()
client.off()

assert list(client.read()) == [
PowerReading(voltage=0.0, current=0.0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ def client(cls) -> str:
return "jumpstarter_driver_power.client.PowerClient"

@abstractmethod
async def on(self) -> str: ...
async def on(self) -> None: ...

@abstractmethod
async def off(self) -> str: ...
async def off(self) -> None: ...

@abstractmethod
async def read(self) -> AsyncGenerator[PowerReading, None]: ...


class MockPower(PowerInterface, Driver):
@export
async def on(self) -> str:
return "ok"
async def on(self) -> None:
pass

@export
async def off(self) -> str:
return "ok"
async def off(self) -> None:
pass

@export
async def read(self) -> AsyncGenerator[PowerReading, None]:
Expand All @@ -37,12 +37,12 @@ async def read(self) -> AsyncGenerator[PowerReading, None]:

class SyncMockPower(PowerInterface, Driver):
@export
def on(self) -> str:
return "ok"
def on(self) -> None:
pass

@export
def off(self) -> str:
return "ok"
def off(self) -> None:
pass

@export
def read(self) -> Generator[PowerReading, None]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
async def test_driver_mock_power():
driver = MockPower()

assert await driver.on() == "ok"
assert await driver.off() == "ok"
await driver.on()
await driver.off()

assert [v async for v in driver.read()] == [
PowerReading(voltage=0.0, current=0.0),
Expand All @@ -21,8 +21,8 @@ async def test_driver_mock_power():
def test_driver_sync_mock_power():
driver = SyncMockPower()

assert driver.on() == "ok"
assert driver.off() == "ok"
driver.on()
driver.off()

assert list(driver.read()) == [
PowerReading(voltage=0.0, current=0.0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

@dataclass(kw_only=True)
class DigitalOutputClient(DriverClient):
def off(self):
def off(self) -> None:
self.call("off")

def on(self):
def on(self) -> None:
self.call("on")


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ def close(self):
super().close()

@export
def off(self):
def off(self) -> None:
if not isinstance(self.device, DigitalOutputDevice):
self.device.close()
self.device = DigitalOutputDevice(pin=self.pin, initial_value=None)
self.device.off()

@export
def on(self):
def on(self) -> None:
if not isinstance(self.device, DigitalOutputDevice):
self.device.close()
self.device = DigitalOutputDevice(pin=self.pin, initial_value=None)
Expand Down
1 change: 1 addition & 0 deletions packages/jumpstarter-driver-tftp/examples/tftp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

log = logging.getLogger(__name__)


class TestResource(JumpstarterTest):
filter_labels = {"board": "rpi4"}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CHUNK_SIZE = 1024 * 1024 * 4 # 4MB
CHUNK_SIZE = 1024 * 1024 * 4 # 4MB
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@

class TftpError(Exception):
"""Base exception for TFTP server errors"""

pass


class ServerNotRunning(TftpError):
"""Server is not running"""

pass


class FileNotFound(TftpError):
"""File not found"""

pass


@dataclass(kw_only=True)
class Tftp(Driver):
"""TFTP Server driver for Jumpstarter
Expand All @@ -40,7 +46,7 @@ class Tftp(Driver):
"""

root_dir: str = "/var/lib/tftpboot"
host: str = field(default='')
host: str = field(default="")
port: int = 69
server: Optional["TftpServer"] = field(init=False, default=None)
server_thread: Optional[threading.Thread] = field(init=False, default=None)
Expand All @@ -53,7 +59,7 @@ def __post_init__(self):
super().__post_init__()

os.makedirs(self.root_dir, exist_ok=True)
if self.host == '':
if self.host == "":
self.host = self.get_default_ip()

def get_default_ip(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ def temp_dir():
with tempfile.TemporaryDirectory() as tmpdir:
yield tmpdir


@pytest.fixture
def server(temp_dir):
server = Tftp(root_dir=temp_dir, host="127.0.0.1")
yield server
server.close()


@pytest.mark.anyio
async def test_tftp_file_operations(server):
filename = "test.txt"
Expand Down Expand Up @@ -60,17 +62,20 @@ async def send_data():
with pytest.raises(FileNotFound):
server.delete_file("nonexistent.txt")


def test_tftp_host_config(temp_dir):
custom_host = "192.168.1.1"
server = Tftp(root_dir=temp_dir, host=custom_host)
assert server.get_host() == custom_host


def test_tftp_root_directory_creation(temp_dir):
new_dir = os.path.join(temp_dir, "new_tftp_root")
server = Tftp(root_dir=new_dir)
assert os.path.exists(new_dir)
server.close()


@pytest.mark.anyio
async def test_tftp_detect_corrupted_file(server):
filename = "corrupted.txt"
Expand All @@ -86,10 +91,12 @@ async def test_tftp_detect_corrupted_file(server):

assert not server.check_file_checksum(filename, client_checksum)


@pytest.fixture
def anyio_backend():
return "asyncio"


async def _upload_file(server, filename: str, data: bytes) -> str:
send_stream, receive_stream = create_memory_object_stream()
resource_uuid = uuid4()
Expand Down
Loading