Mr2 test harness

Better directory handling in the testing environment, and new tests.

Merge request reports

Loading
+33 −0
Changes for tests/integration/compatibility/test_compat.py: 33 added lines, 0 removed lines.
Original line number Diff line number Diff line
import pytest

def test_compat_cp_mv(gkfs_daemon, gkfs_shell):
    """
    Test cp and mv compatibility.
    """
    cmd = gkfs_shell.script(
        f"""
            mkdir -p {gkfs_daemon.mountdir / 'compat'}
            echo "data" > {gkfs_daemon.mountdir / 'compat/file1'}
            cp {gkfs_daemon.mountdir / 'compat/file1'} {gkfs_daemon.mountdir / 'compat/file2'}
            mv {gkfs_daemon.mountdir / 'compat/file2'} {gkfs_daemon.mountdir / 'compat/file3'}
            
            diff {gkfs_daemon.mountdir / 'compat/file1'} {gkfs_daemon.mountdir / 'compat/file3'}
        """)
    if cmd.exit_code != 0:
        import sys
        sys.stderr.write(f"compat_cp_mv failed. stdout: {cmd.stdout.decode()} stderr: {cmd.stderr.decode()}")
    assert cmd.exit_code == 0

def test_compat_grep(gkfs_daemon, gkfs_shell):
    cmd = gkfs_shell.script(
        f"""
            mkdir -p {gkfs_daemon.mountdir / 'grep_dir'}
            echo "hello world" > {gkfs_daemon.mountdir / 'grep_dir/f1'}
            echo "goodbye" > {gkfs_daemon.mountdir / 'grep_dir/f2'}
            
            grep "hello" {gkfs_daemon.mountdir / 'grep_dir/f1'}
            if [ $? -ne 0 ]; then exit 1; fi
            
            grep "world" {gkfs_daemon.mountdir / 'grep_dir'}/*
        """)
    assert cmd.exit_code == 0
+69 −0
Changes for tests/integration/compatibility/test_standard_tools.py: 69 added lines, 0 removed lines.
Original line number Diff line number Diff line
import pytest
import hashlib

@pytest.mark.parametrize("shell_fixture", ["gkfs_shell", "gkfs_shellLibc"])
def test_tar_extract(gkfs_daemon, shell_fixture, test_workspace, request):
    """
    Test tar extraction onto GekkoFS.
    """
    gkfs_shell = request.getfixturevalue(shell_fixture)
    # Create a local tar file (not in GekkoFS)
    local_tar = test_workspace.twd / "payload.tar"
    cmd = gkfs_shell.script(
        f"""
        mkdir -p /tmp/payload_src/subdir
        echo "stuff" > /tmp/payload_src/file1
        echo "more" > /tmp/payload_src/subdir/file2
        tar -cf {local_tar} -C /tmp/payload_src .
        rm -rf /tmp/payload_src
        """, intercept_shell=False) # Run natively
    assert cmd.exit_code == 0
    
    # Extract into GekkoFS
    cmd = gkfs_shell.script(
        f"""
        mkdir -p {gkfs_daemon.mountdir / 'tar_target'}
        tar -xf {local_tar} -C {gkfs_daemon.mountdir / 'tar_target'}
        exit $?
        """)
    assert cmd.exit_code == 0
    
    # Verify content
    cmd = gkfs_shell.script(
        f"""
        cat {gkfs_daemon.mountdir / 'tar_target/file1'}
        cat {gkfs_daemon.mountdir / 'tar_target/subdir/file2'}
        """)
    assert "stuff" in cmd.stdout.decode()
    assert "more" in cmd.stdout.decode()

@pytest.mark.parametrize("shell_fixture", ["gkfs_shell", "gkfs_shellLibc"])
def test_rm_recursive(gkfs_daemon, shell_fixture, request):
    """
    Test rm -rf directories.
    """
    gkfs_shell = request.getfixturevalue(shell_fixture)
    cmd = gkfs_shell.script(
        f"""
        mkdir -p {gkfs_daemon.mountdir / 'delete_me/nested'}
        echo "val" > {gkfs_daemon.mountdir / 'delete_me/nested/f'}
        rm -rf {gkfs_daemon.mountdir / 'delete_me'}
        if [ -e {gkfs_daemon.mountdir / 'delete_me'} ]; then exit 1; fi
        exit 0
        """)
    assert cmd.exit_code == 0

@pytest.mark.parametrize("shell_fixture", ["gkfs_shell", "gkfs_shellLibc"])
def test_md5sum(gkfs_daemon, shell_fixture, request):
    """
    Test reading files with checksum tools.
    """
    gkfs_shell = request.getfixturevalue(shell_fixture)
    cmd = gkfs_shell.script(
        f"""
        echo "checksum_this" > {gkfs_daemon.mountdir / 'chk_file'}
        md5sum {gkfs_daemon.mountdir / 'chk_file'}
        """)
    assert cmd.exit_code == 0
    # md5sum of "checksum_this\n" is usually ...
    # We just check exit code here, ensuring read works.
+75 −0
Changes for tests/integration/concurrency/test_concurrency.py: 75 added lines, 0 removed lines.
Original line number Diff line number Diff line
import pytest
from harness.logger import logger
import time

def test_concurrent_create(gkfs_daemon, gkfs_shell):
    """
    Test concurrent file creation in the same directory.
    Using background processes via shell script.
    """
    cmd = gkfs_shell.script(
        f"""
            GKFS_LOG=info mkdir -p {gkfs_daemon.mountdir / 'concurrent_dir'} > /tmp/mkdir_check.log 2>&1
            ls -ld {gkfs_daemon.mountdir / 'concurrent_dir'} >> /tmp/mkdir_check.log 2>&1
            
            # Start 10 background processes creating files via nested bash with env vars SET BEFORE EXEC
            # Use touch to ensure openat is used correctly
            GKFS_LOG=info GKFS_LOG_OUTPUT=/tmp/gkfs_client_conc.log bash -c "echo 'STARTING LOOP'; for i in \$(seq 1 10); do touch \\\"{gkfs_daemon.mountdir / 'concurrent_dir'}/file_\$i\\\" & done; echo 'WAITING'; wait" > /tmp/loop_output.log 2>&1
            
            
            # Wait for all background jobs
            wait
            
            # Verify count
            ls -1 "{gkfs_daemon.mountdir / 'concurrent_dir'}" | wc -l
        """)
    
    if cmd.exit_code != 0:
        import sys
        sys.stderr.write(f"concurrent_create failed. stdout: {cmd.stdout.decode()} stderr: {cmd.stderr.decode()}")

    assert cmd.exit_code == 0
    assert int(cmd.stdout.decode().strip()) == 10

def test_concurrent_write_shared_file(gkfs_daemon, gkfs_shell):
    """
    Test concurrent writes to the SAME file (append).
    Note: GekkoFS might not support atomic append perfectly but shouldn't crash.
    """
    cmd = gkfs_shell.script(
        f"""
           echo "" > {gkfs_daemon.mountdir / 'shared_file'}
           
           for i in $(seq 1 10); do
               echo "line_$i" >> "{gkfs_daemon.mountdir / 'shared_file'}" &
           done
           
           wait
           
           wc -l < "{gkfs_daemon.mountdir / 'shared_file'}"
        """)
    assert cmd.exit_code == 0
    # We expect 10 lines (plus initial empty line = 11? or just 10 if echo "" creates 1 line)
    # echo "" creates newline. echo "line" >> appends.
    # Total 11 lines.
    lines = int(cmd.stdout.decode().strip())
    assert lines == 11

def test_concurrent_read(gkfs_daemon, gkfs_shell):
    """
    Test concurrent reads from the same file.
    """
    cmd = gkfs_shell.script(
        f"""
            # Create 1MB file
            head -c 1048576 /dev/urandom > {gkfs_daemon.mountdir / 'read_file'}
            
            # 5 readers
            for i in $(seq 1 5); do
                cat {gkfs_daemon.mountdir / 'read_file'} > /dev/null &
            done
            
            wait
            exit 0
        """)
    assert cmd.exit_code == 0
+86 −0
Changes for tests/integration/data/test_chunk_stat.py: 86 added lines, 0 removed lines.
Original line number Diff line number Diff line
import pytest
from harness.logger import logger
import os

@pytest.mark.parametrize("client_fixture", ["gkfs_client"])
def test_chunk_stat_update(test_workspace, gkfs_daemon, client_fixture, request):
    """
    Verify that statfs reports correct block counts and updates after writing data.
    """
    
    # Get the appropriate client fixture
    client = request.getfixturevalue(client_fixture)
    
    # Verify initial state
    ret = client.statfs(gkfs_daemon.mountdir)
    assert ret.retval == 0
    assert ret.statfsbuf.f_bsize > 0
    assert ret.statfsbuf.f_blocks > 0
    assert ret.statfsbuf.f_bfree <= ret.statfsbuf.f_blocks
    
    initial_free = ret.statfsbuf.f_bfree
    chunk_size = ret.statfsbuf.f_bsize
    
    # Write one chunk of data
    file_path = gkfs_daemon.mountdir / "test_file"
    
    # We must write in small chunks because gkfs.io receives data as a CLI argument,
    # and Linux imposes a limit (MAX_ARG_STRLEN ~128KB).
    chunk_write_size = 100 * 1024 
    total_write_len = 5 * 1024 * 1024
    
    # Ensure file exists (gkfs.io write with append might need it, or we use -c if available, 
    # but strictly speaking client.write opens it. Let's rely on write creating it or failing if not - 
    # wait, gkfs.io write -c creates it. client.write(path, data, count, append) maps to 
    # gkfs.io write path data count append.
    # It does NOT pass -c. 
    # So we DO need to create it. client.open creates it and closes it.
    
    ret = client.open(file_path, os.O_CREAT | os.O_WRONLY)
    assert ret.retval != -1
    # File created.
    
    buf = b'X' * chunk_write_size
    written = 0
    while written < total_write_len:
        # gkfs.io write arguments: pathname data count [append]
        # We append (1) to accumulate data.
        ret = client.write(file_path, buf, chunk_write_size, 1) # 1 for append
        assert ret.retval == chunk_write_size
        written += chunk_write_size
    
    # client.close(fd) # No persistent FD to close
    
    # Verify updated state
    ret = client.statfs(gkfs_daemon.mountdir)
    assert ret.retval == 0
    
    # Check that free blocks decremented
    # Note: implementation detail - how many blocks does GekkoFS consume?
    # It should be at least (write_len / chunk_size) rounded up.
    
    # GekkoFS hardcodes f_type and file counts to 0 currently
    assert ret.statfsbuf.f_type == 0
    assert ret.statfsbuf.f_files == 0
    assert ret.statfsbuf.f_ffree == 0

    consumed_blocks = initial_free - ret.statfsbuf.f_bfree

    
    # We wrote 5MB. GekkoFS chunk size is typically 512KB.
    # So we expect 10 chunks to be consumed.
    # Since we are backed by real FS, block alignment might cause variance,
    # but it should be at least (total_write_len / chunk_size).
    expected_blocks = total_write_len // chunk_size
    assert consumed_blocks >= expected_blocks

    # Clean up file
    ret = client.unlink(file_path)
    assert ret.retval == 0

    # Note: GekkoFS removes chunks immediately (unlink -> destroy_chunk_space -> fs::remove_all)
    # So space should be reclaimed.
    
    ret = client.statfs(gkfs_daemon.mountdir)
    assert ret.retval == 0
    
 No newline at end of file
+14 −10
Changes for tests/integration/data/test_data_integrity.py: 14 added lines, 10 removed lines.
Original line number Diff line number Diff line
@@ -64,7 +64,7 @@ def test_data_integrity(gkfs_daemon, gkfs_client):
    topdir = gkfs_daemon.mountdir / "top"
    file_a = topdir / "file_a"

    # create topdir
    print("DEBUG: Creating topdir", file=sys.stderr)
    ret = gkfs_client.mkdir(
            topdir,
            stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
@@ -72,11 +72,13 @@ def test_data_integrity(gkfs_daemon, gkfs_client):
    assert ret.retval == 0

    # test stat on existing dir
    print("DEBUG: Stat topdir", file=sys.stderr)
    ret = gkfs_client.stat(topdir)

    assert ret.retval == 0
    assert (stat.S_ISDIR(ret.statbuf.st_mode))

    print("DEBUG: Open file_a", file=sys.stderr)
    ret = gkfs_client.open(file_a,
                   os.O_CREAT,
                   stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
@@ -85,6 +87,7 @@ def test_data_integrity(gkfs_daemon, gkfs_client):


    # test stat on existing file
    print("DEBUG: Stat file_a", file=sys.stderr)
    ret = gkfs_client.stat(file_a)

    assert ret.retval == 0
@@ -97,36 +100,37 @@ def test_data_integrity(gkfs_daemon, gkfs_client):
    # Read data
    # Compare buffer

    print("DEBUG: write_validate 1", file=sys.stderr)
    ret = gkfs_client.write_validate(file_a, 1)
    assert ret.retval == 1    
    assert ret.retval == 0    

    ret = gkfs_client.write_validate(file_a, 256)
    assert ret.retval == 1  
    assert ret.retval == 0  

    ret = gkfs_client.write_validate(file_a, 512)
    assert ret.retval == 1  
    assert ret.retval == 0  

    # Step 2 - Compare bigger sizes exceeding typical chunksize and not aligned
    ret = gkfs_client.write_validate(file_a, 128192)
    assert ret.retval == 1
    assert ret.retval == 0

    # < 1 chunk   
    ret = gkfs_client.write_validate(file_a, 400000)
    assert ret.retval == 1
    assert ret.retval == 0

    # > 1 chunk < 2 chunks
    ret = gkfs_client.write_validate(file_a, 600000)
    assert ret.retval == 1
    assert ret.retval == 0

    # > 1 chunk < 2 chunks
    ret = gkfs_client.write_validate(file_a, 900000)
    assert ret.retval == 1
    assert ret.retval == 0

    # > 2 chunks
    ret = gkfs_client.write_validate(file_a, 1100000) 
    assert ret.retval == 1
    assert ret.retval == 0

    # > 4 chunks
    ret = gkfs_client.write_validate(file_a, 2097153) 
    assert ret.retval == 1
    assert ret.retval == 0
Loading
Loading