Skip to content
Open
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
131 changes: 131 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,134 @@ def trace_with_unordered(tmp_path: Path) -> Path:
trace_file = tmp_path.joinpath("trace_with_unordered.out")
trace_file.write_text(content)
return trace_file


@pytest.fixture
def trace_with_malformed_fetch(tmp_path: Path) -> Path:
content = """\
O3PipeView:fetch:1000:0x1000:0:add x1, x2, x3:IntAlu
O3PipeView:decode:1100
O3PipeView:fetch:2000:0x2000:0:2:lw a0, 0(a1):MemRead
O3PipeView:decode:2100
O3PipeView:retire:2500:store:0
"""
trace_file = tmp_path.joinpath("trace_malformed.out")
trace_file.write_text(content)
return trace_file


@pytest.fixture
def trace_with_store_completion(tmp_path: Path) -> Path:
content = """\
O3PipeView:fetch:1000:0x1000:0:1:sw a0, 0(a1):MemWrite
O3PipeView:decode:1100
O3PipeView:rename:1150
O3PipeView:dispatch:1200
O3PipeView:issue:1300
O3PipeView:complete:1400
O3PipeView:retire:1500:store:2000
"""
trace_file = tmp_path.joinpath("trace_store.out")
trace_file.write_text(content)
return trace_file


@pytest.fixture
def trace_with_invalid_issue(tmp_path: Path) -> Path:
content = """\
O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu
O3PipeView:decode:1100
O3PipeView:rename:1150
O3PipeView:dispatch:1200
O3PipeView:issue:0
O3PipeView:complete:1400
O3PipeView:retire:1500:store:0
"""
trace_file = tmp_path.joinpath("trace_invalid_issue.out")
trace_file.write_text(content)
return trace_file


@pytest.fixture
def trace_with_empty_disasm(tmp_path: Path) -> Path:
content = """\
O3PipeView:fetch:1000:0x1000:0:1::No_OpClass
O3PipeView:decode:1100
O3PipeView:rename:1150
O3PipeView:dispatch:1200
O3PipeView:issue:1300
O3PipeView:complete:1400
O3PipeView:retire:1500:store:0
"""
trace_file = tmp_path.joinpath("trace_empty_disasm.out")
trace_file.write_text(content)
return trace_file


@pytest.fixture
def trace_empty(tmp_path: Path) -> Path:
trace_file = tmp_path.joinpath("trace_empty.out")
trace_file.write_text("")
return trace_file


@pytest.fixture
def squashed_parser() -> PipeViewParser:
parser = PipeViewParser()
instr = Instruction(
seq_num=2,
pc="0x2000",
disasm="lw a0, 0(a1)",
opclass="MemRead",
stages={
PipelineStage.FETCH: 1000,
PipelineStage.DECODE: 1100,
PipelineStage.RENAME: 0,
PipelineStage.DISPATCH: 0,
PipelineStage.ISSUE: 0,
PipelineStage.COMPLETE: 0,
PipelineStage.RETIRE: 0,
},
stage_order=[
PipelineStage.FETCH,
PipelineStage.DECODE,
PipelineStage.RENAME,
PipelineStage.DISPATCH,
PipelineStage.ISSUE,
PipelineStage.COMPLETE,
PipelineStage.RETIRE,
],
)
parser.instructions = {2: instr}
return parser


@pytest.fixture
def parser_with_unknown_opclass() -> PipeViewParser:
parser = PipeViewParser()
instr = Instruction(
seq_num=3,
pc="0x3000",
disasm="fence",
opclass="SomeUnknownOpClass",
stages={
PipelineStage.FETCH: 1000,
PipelineStage.DECODE: 1100,
PipelineStage.RENAME: 1150,
PipelineStage.DISPATCH: 1200,
PipelineStage.ISSUE: 1300,
PipelineStage.COMPLETE: 1400,
PipelineStage.RETIRE: 1500,
},
stage_order=[
PipelineStage.FETCH,
PipelineStage.DECODE,
PipelineStage.RENAME,
PipelineStage.DISPATCH,
PipelineStage.ISSUE,
PipelineStage.COMPLETE,
PipelineStage.RETIRE,
],
)
parser.instructions = {3: instr}
return parser
16 changes: 16 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,19 @@ def test_config_load_from_nonexist_dir(tmp_path: Path):
nonexist = tmp_path.joinpath("nonexist")
config = load_config(nonexist)
assert config.get_stage_name(PipelineStage.FETCH) is not None


def test_config_getattr_missing_key(config: Config):
with pytest.raises(AttributeError):
_ = config._no_such_key


def test_config_path_not_directory(tmp_path: Path, caplog):
config_file = tmp_path.joinpath("colors.json")
config_file.write_text(json.dumps({"default": ["test"]}))

import logging
caplog.set_level(logging.WARNING)
config = load_config(config_file)
assert config.get_stage_name(PipelineStage.FETCH) is not None
assert "not a directory" in caplog.text
179 changes: 179 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
import sys
import json
import logging
from pathlib import Path

from uScope.main import main
Expand Down Expand Up @@ -32,3 +33,181 @@ def test_main_basic(tmp_path: Path, monkeypatch):
assert output_file.exists()
data = json.loads(output_file.read_text())
assert isinstance(data, list)


def test_main_verbose(tmp_path: Path, monkeypatch, caplog):
input_file = tmp_path.joinpath("trace.out")
input_file.write_text(
"O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu\n"
"O3PipeView:decode:1100\n"
"O3PipeView:rename:1150\n"
"O3PipeView:dispatch:1200\n"
"O3PipeView:issue:1300\n"
"O3PipeView:complete:1400\n"
"O3PipeView:retire:1500:store:0\n"
)
output_file = tmp_path.joinpath("output.json")

caplog.set_level(logging.DEBUG)
monkeypatch.setattr(
sys, "argv",
["uscope", "-v", "--input-file", str(input_file), "--output-file", str(output_file)],
)
main()
assert output_file.exists()


def test_main_quiet(tmp_path: Path, monkeypatch, caplog):
input_file = tmp_path.joinpath("trace.out")
input_file.write_text(
"O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu\n"
"O3PipeView:decode:1100\n"
"O3PipeView:rename:1150\n"
"O3PipeView:dispatch:1200\n"
"O3PipeView:issue:1300\n"
"O3PipeView:complete:1400\n"
"O3PipeView:retire:1500:store:0\n"
)
output_file = tmp_path.joinpath("output.json")

monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "--input-file", str(input_file), "--output-file", str(output_file)],
)
main()
assert output_file.exists()


def test_main_gzip(tmp_path: Path, monkeypatch):
input_file = tmp_path.joinpath("trace.out")
input_file.write_text(
"O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu\n"
"O3PipeView:decode:1100\n"
"O3PipeView:rename:1150\n"
"O3PipeView:dispatch:1200\n"
"O3PipeView:issue:1300\n"
"O3PipeView:complete:1400\n"
"O3PipeView:retire:1500:store:0\n"
)
output_file = tmp_path.joinpath("output.json.gz")

monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "-z", "--input-file", str(input_file), "--output-file", str(output_file)],
)
main()
assert output_file.exists()
import gzip
data = json.loads(gzip.open(output_file, 'rt').read())
assert isinstance(data, list)


def test_main_empty_trace(tmp_path: Path, monkeypatch):
input_file = tmp_path.joinpath("trace.out")
input_file.write_text("")
output_file = tmp_path.joinpath("output.json")

monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "--input-file", str(input_file), "--output-file", str(output_file)],
)
with pytest.raises(SystemExit) as exc:
main()
assert exc.value.code == 1


def test_main_file_not_found(tmp_path: Path, monkeypatch):
monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "--input-file", str(tmp_path / "nonexistent.out"), "--output-file", str(tmp_path / "out.json")],
)
with pytest.raises(SystemExit) as exc:
main()
assert exc.value.code == 2


def test_main_non_out_extension(tmp_path: Path, monkeypatch):
input_file = tmp_path.joinpath("trace.txt")
input_file.write_text(
"O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu\n"
"O3PipeView:decode:1100\n"
"O3PipeView:rename:1150\n"
"O3PipeView:dispatch:1200\n"
"O3PipeView:issue:1300\n"
"O3PipeView:complete:1400\n"
"O3PipeView:retire:1500:store:0\n"
)
output_file = tmp_path.joinpath("output.json")

monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "--input-file", str(input_file), "--output-file", str(output_file)],
)
main()
assert output_file.exists()


def test_main_auto_output_name(tmp_path: Path, monkeypatch):
input_file = tmp_path.joinpath("trace.out")
input_file.write_text(
"O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu\n"
"O3PipeView:decode:1100\n"
"O3PipeView:rename:1150\n"
"O3PipeView:dispatch:1200\n"
"O3PipeView:issue:1300\n"
"O3PipeView:complete:1400\n"
"O3PipeView:retire:1500:store:0\n"
)
expected_output = tmp_path.joinpath("trace.json")

monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "--input-file", str(input_file)],
)
main()
assert expected_output.exists()


def test_main_gzip_auto_extension(tmp_path: Path, monkeypatch):
input_file = tmp_path.joinpath("trace.out")
input_file.write_text(
"O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu\n"
"O3PipeView:decode:1100\n"
"O3PipeView:rename:1150\n"
"O3PipeView:dispatch:1200\n"
"O3PipeView:issue:1300\n"
"O3PipeView:complete:1400\n"
"O3PipeView:retire:1500:store:0\n"
)
expected_output = tmp_path.joinpath("trace.json.gz")

monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "-z", "--input-file", str(input_file)],
)
main()
assert expected_output.exists()
import gzip
data = json.loads(gzip.open(expected_output, 'rt').read())
assert isinstance(data, list)


def test_main_auto_output_txt_extension(tmp_path: Path, monkeypatch):
input_file = tmp_path.joinpath("trace.txt")
input_file.write_text(
"O3PipeView:fetch:1000:0x1000:0:1:add x1, x2, x3:IntAlu\n"
"O3PipeView:decode:1100\n"
"O3PipeView:rename:1150\n"
"O3PipeView:dispatch:1200\n"
"O3PipeView:issue:1300\n"
"O3PipeView:complete:1400\n"
"O3PipeView:retire:1500:store:0\n"
)
expected_output = tmp_path.joinpath("trace.txt.json")

monkeypatch.setattr(
sys, "argv",
["uscope", "-q", "--input-file", str(input_file)],
)
main()
assert expected_output.exists()
44 changes: 44 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,47 @@ def test_parse_unordered(trace_with_unordered):
),
}
assert_instructions(parser, expected, all_stages)


def test_parse_malformed_fetch(trace_with_malformed_fetch):
parser = PipeViewParser()
parser.parse_file(str(trace_with_malformed_fetch))

all_stages = PipelineStage.order()
expected = {
2: (
"0x2000",
"lw a0, 0(a1)",
"MemRead",
{
PipelineStage.FETCH: 2000,
PipelineStage.DECODE: 2100,
PipelineStage.RENAME: 0,
PipelineStage.DISPATCH: 0,
PipelineStage.ISSUE: 0,
PipelineStage.COMPLETE: 0,
PipelineStage.RETIRE: 2500,
},
),
}
assert len(parser.instructions) == len(expected)
assert parser.instructions[2].disasm == "lw a0, 0(a1)"


def test_empty_disasm_mnemonic():
from uScope.O3 import Instruction

instr = Instruction(
seq_num=1, pc="0x0", disasm="", opclass="No_OpClass",
stages={}, stage_order=[]
)
assert instr.mnemonic == Instruction.UNKNOWN


def test_store_completion_parsed(trace_with_store_completion):
parser = PipeViewParser()
parser.parse_file(str(trace_with_store_completion))

instr = parser.instructions[1]
assert instr.store_tick == 2000
assert instr.stages[PipelineStage.RETIRE] == 1500
Loading