Skip to content

Commit 1efa2da

Browse files
authored
Merge pull request #265 from jku/cleanup-refactor-faketime-use
Cleanup & refactor faketime use
2 parents 9b315ba + a275180 commit 1efa2da

20 files changed

+97
-49
lines changed

clients/go-tuf/cmd/download.go

+18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import (
1515
"fmt"
1616
"net/url"
1717
"os"
18+
"os/exec"
1819
"path/filepath"
20+
"strconv"
21+
"time"
1922

2023
log "github.com/sirupsen/logrus"
2124
"github.com/spf13/cobra"
@@ -85,6 +88,21 @@ func RefreshAndDownloadCmd(targetName string,
8588
return fmt.Errorf("failed to create Updater instance: %w", err)
8689
}
8790

91+
// Test suite uses faketime to set the time client should consider "current".
92+
// Golang does not use libc and bypasses the tricks faketime uses.
93+
// use "date" command to get current time as this is affected by faketime
94+
out, err := exec.Command("date", "+%s").Output()
95+
if err != nil {
96+
return fmt.Errorf("calling 'date' failed: %w", err)
97+
}
98+
timestamp_str := string(out[:len(out)-1])
99+
timestamp, err := strconv.ParseInt(timestamp_str, 10, 64)
100+
if err != nil {
101+
return fmt.Errorf("failed to parse date output: %w", err)
102+
}
103+
ref_time := time.Unix(timestamp, 0)
104+
up.UnsafeSetRefTime(ref_time)
105+
88106
// try to build the top-level metadata
89107
err = up.Refresh()
90108
if err != nil {

tuf_conformance/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
__version__ = "2.2.0"
44

55
# register pytest asserts before the imports happen in conftest.py
6-
pytest.register_assert_rewrite("tuf_conformance.client_runner")
6+
pytest.register_assert_rewrite("tuf_conformance._internal.client_runner")

tuf_conformance/client_runner.py tuf_conformance/_internal/client_runner.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import glob
22
import os
33
import subprocess
4+
from datetime import datetime
45
from tempfile import TemporaryDirectory
56

67
from tuf.api.exceptions import StorageError
78
from tuf.api.metadata import Metadata
89
from tuf.api.serialization.json import JSONSerializer
910

10-
from tuf_conformance.metadata import MetadataTest
11-
from tuf_conformance.simulator_server import (
11+
from tuf_conformance._internal.metadata import MetadataTest
12+
from tuf_conformance._internal.simulator_server import (
1213
ClientInitData,
1314
SimulatorServer,
1415
StaticServer,
@@ -62,13 +63,13 @@ def init_client(self, data: ClientInitData) -> int:
6263
cmd = [*self._cmd, "--metadata-dir", self.metadata_dir, "init", trusted]
6364
return self._run(cmd)
6465

65-
def refresh(self, data: ClientInitData, days_in_future: int = 0) -> int:
66+
def refresh(self, data: ClientInitData, fake_time: datetime | None = None) -> int:
6667
# dump a repository version for each client refresh (if configured to)
6768
self._server.debug_dump(self.test_name)
6869

6970
cmd = self._cmd
70-
if days_in_future:
71-
cmd = ["faketime", "-f", f"+{days_in_future}d", *cmd]
71+
if fake_time:
72+
cmd = ["faketime", f"{fake_time}", *cmd]
7273

7374
cmd = [
7475
*cmd,
File renamed without changes.

tuf_conformance/repository_simulator.py tuf_conformance/_internal/repository_simulator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
)
5353
from tuf.api.serialization.json import JSONSerializer
5454

55-
from tuf_conformance.metadata import MetadataTest, RootTest
55+
from tuf_conformance._internal.metadata import MetadataTest, RootTest
5656

5757
logger = logging.getLogger(__name__)
5858

tuf_conformance/simulator_server.py tuf_conformance/_internal/simulator_server.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import os
22
from dataclasses import dataclass
3+
from datetime import UTC, datetime
34
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
45
from os import path
56
from urllib import parse
67

7-
from tuf_conformance.repository_simulator import RepositorySimulator
8+
from tuf_conformance._internal.repository_simulator import RepositorySimulator
89

910

1011
@dataclass
@@ -87,7 +88,9 @@ def debug_dump(self, test_name: str) -> None:
8788
class StaticServer(ThreadingHTTPServer):
8889
"""Web server to serve static repositories"""
8990

90-
data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static_data")
91+
data_dir = os.path.join(
92+
os.path.dirname(os.path.abspath(__file__)), "..", "static_data"
93+
)
9194

9295
@classmethod
9396
def static_test_names(cls) -> list[str]:
@@ -117,7 +120,7 @@ def do_GET(self) -> None: # noqa: N802
117120
super().__init__(("127.0.0.1", 0), _StaticReqHandler)
118121
self.timeout = 0
119122

120-
def new_test(self, static_dir: str) -> tuple[ClientInitData, str]:
123+
def new_test(self, static_dir: str) -> tuple[ClientInitData, str, datetime]:
121124
sub_dir = os.path.join(self.data_dir, static_dir)
122125
with open(os.path.join(sub_dir, "initial_root.json"), "rb") as f:
123126
initial_root = f.read()
@@ -133,7 +136,13 @@ def new_test(self, static_dir: str) -> tuple[ClientInitData, str]:
133136
with open(os.path.join(sub_dir, "targetpath")) as f:
134137
targetpath = f.readline().strip("\n")
135138

136-
return client_data, targetpath
139+
try:
140+
with open(os.path.join(sub_dir, "faketime")) as f:
141+
faketime = datetime.fromisoformat(f.readline().strip("\n"))
142+
except OSError:
143+
faketime = datetime.now(UTC)
144+
145+
return client_data, targetpath, faketime
137146

138147
def debug_dump(self, test_name: str) -> None:
139148
pass # not implemented
File renamed without changes.

tuf_conformance/conftest.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
import pytest
55

6-
from tuf_conformance.client_runner import ClientRunner
7-
from tuf_conformance.simulator_server import SimulatorServer, StaticServer
6+
from tuf_conformance._internal.client_runner import ClientRunner
7+
from tuf_conformance._internal.simulator_server import SimulatorServer, StaticServer
88

99

1010
def pytest_addoption(parser: pytest.Parser) -> None:

tuf_conformance/static_data/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
Subdirectories should contain complete repositories produced by a specific repository
44
implementation. Each repository in a `<SUBDIR>` should
55
* demonstrate all of the TUF features that the implementation uses
6-
* not expire for a very long time
76
* Store metadata in `<SUBDIR/metadata>` and artifacts in `<SUBDIR/targets>`
87
* be ready to be published with just `python -m http.server <SUBDIR>` (in other words filenames
98
should match the TUF http API)
109

1110
Additionally there should be
1211
* A version of root in `<SUBDIR>/initial_root.json`: This will be used to initialize the client
1312
* `<SUBDIR>/targetpath` containing a targetpath of an artifact that exists in the repository
13+
* optional `<SUBDIR>/faketime` containing a ISO 8601 date that will be used as the refresh time for the client
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2025-02-09 09:17:23+00:00

tuf_conformance/test_basic.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import json
22
import os
3+
from datetime import UTC, datetime, timedelta
34

45
import pytest
56
from securesystemslib.formats import encode_canonical
67
from securesystemslib.hash import digest
78
from tuf.api.metadata import Key, Metadata, MetaFile, Root, Snapshot, Targets, Timestamp
89

9-
from tuf_conformance.client_runner import ClientRunner
10-
from tuf_conformance.simulator_server import SimulatorServer
10+
from tuf_conformance._internal.client_runner import ClientRunner
11+
from tuf_conformance._internal.simulator_server import SimulatorServer
1112

1213

1314
def recalculate_keyid(key: Key) -> None:
@@ -414,3 +415,22 @@ def test_incorrect_metadata_type(client: ClientRunner, server: SimulatorServer)
414415
# Client should refuse timestamp that has incorrect type field
415416
assert client.refresh(init_data) == 1
416417
assert client.trusted_roles() == [(Root.type, 2)]
418+
419+
420+
def test_faketime(client: ClientRunner, server: SimulatorServer) -> None:
421+
"""Ensures that client supports the faketime setup in this test suite"""
422+
init_data, repo = server.new_test(client.test_name)
423+
424+
assert client.init_client(init_data) == 0
425+
426+
# root v2 expires in 7 days
427+
now = datetime.now(UTC)
428+
repo.root.expires = now + timedelta(days=7)
429+
repo.publish([Root.type])
430+
431+
# Refresh
432+
assert client.refresh(init_data) == 0
433+
434+
# Mock time so that root has expired. If client unexpectedly succeeds here,
435+
# it likely does not work with faketime
436+
assert client.refresh(init_data, now + timedelta(days=8)) == 1

tuf_conformance/test_expiration.py

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import datetime
2-
from datetime import timezone
1+
from datetime import UTC, datetime, timedelta
32

43
from tuf.api.metadata import Root, Snapshot, Targets, Timestamp
54

6-
from tuf_conformance import utils
7-
from tuf_conformance.client_runner import ClientRunner
8-
from tuf_conformance.simulator_server import SimulatorServer
5+
from tuf_conformance._internal import utils
6+
from tuf_conformance._internal.client_runner import ClientRunner
7+
from tuf_conformance._internal.simulator_server import SimulatorServer
98

109

1110
def test_root_expired(client: ClientRunner, server: SimulatorServer) -> None:
@@ -113,19 +112,19 @@ def test_expired_local_root(client: ClientRunner, server: SimulatorServer) -> No
113112
assert client.init_client(init_data) == 0
114113

115114
# root v2 expires in 7 days
116-
now = datetime.datetime.now(timezone.utc)
117-
repo.root.expires = now + datetime.timedelta(days=7)
115+
now = datetime.now(UTC)
116+
repo.root.expires = now + timedelta(days=7)
118117
repo.publish([Root.type])
119118

120119
# Refresh
121120
assert client.refresh(init_data) == 0
122121

123122
# root v3 expires in 21 days
124-
repo.root.expires = now + datetime.timedelta(days=21)
123+
repo.root.expires = now + timedelta(days=21)
125124
repo.publish([Root.type])
126125

127126
# Mocking time so that local root (v2) has expired but v3 from repo has not
128-
assert client.refresh(init_data, days_in_future=18) == 0
127+
assert client.refresh(init_data, now + timedelta(days=18)) == 0
129128
assert client.version(Root.type) == 3
130129

131130

@@ -145,21 +144,21 @@ def test_expired_local_timestamp(client: ClientRunner, server: SimulatorServer)
145144
assert client.init_client(init_data) == 0
146145

147146
# Repo timestamp v1 expires in 7 days
148-
now = datetime.datetime.now(timezone.utc)
149-
repo.timestamp.expires = now + datetime.timedelta(days=7)
147+
now = datetime.now(UTC)
148+
repo.timestamp.expires = now + timedelta(days=7)
150149
repo.publish([Timestamp.type]) # v2
151150

152151
# Refresh
153152
assert client.refresh(init_data) == 0
154153

155154
# Bump targets + snapshot version
156155
# Set next version of repo timestamp to expire in 21 days
157-
repo.timestamp.expires = now + datetime.timedelta(days=21)
156+
repo.timestamp.expires = now + timedelta(days=21)
158157
repo.publish([Targets.type, Snapshot.type, Timestamp.type]) # v2, v2, v3
159158

160159
# Mocking time so that local timestamp has expired
161160
# but the new timestamp has not
162-
assert client.refresh(init_data, days_in_future=18) == 0
161+
assert client.refresh(init_data, now + timedelta(days=18)) == 0
163162

164163
# Assert final versions of timestamp/snapshot
165164
# which means a successful refresh is performed

tuf_conformance/test_file_download.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import pytest
22
from tuf.api.metadata import Snapshot, TargetFile, Targets, Timestamp
33

4-
from tuf_conformance.client_runner import ClientRunner
5-
from tuf_conformance.repository_simulator import Artifact
6-
from tuf_conformance.simulator_server import SimulatorServer
4+
from tuf_conformance._internal.client_runner import ClientRunner
5+
from tuf_conformance._internal.repository_simulator import Artifact
6+
from tuf_conformance._internal.simulator_server import SimulatorServer
77

88

99
def test_client_downloads_expected_file(

tuf_conformance/test_keys.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import pytest
44
from tuf.api.metadata import Metadata, Root, Snapshot, Targets, Timestamp
55

6-
from tuf_conformance.client_runner import ClientRunner
7-
from tuf_conformance.simulator_server import SimulatorServer
6+
from tuf_conformance._internal.client_runner import ClientRunner
7+
from tuf_conformance._internal.simulator_server import SimulatorServer
88

99

1010
def test_snapshot_does_not_meet_threshold(

tuf_conformance/test_quoting_issues.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import pytest
44
from tuf.api.metadata import DelegatedRole, Snapshot, Targets, Timestamp
55

6-
from tuf_conformance.client_runner import ClientRunner
7-
from tuf_conformance.simulator_server import SimulatorServer
6+
from tuf_conformance._internal.client_runner import ClientRunner
7+
from tuf_conformance._internal.simulator_server import SimulatorServer
88

99
unusual_role_names = [
1010
"?",

tuf_conformance/test_repository_simulator.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
Timestamp,
1010
)
1111

12-
from tuf_conformance import utils
13-
from tuf_conformance.simulator_server import SimulatorServer
12+
from tuf_conformance._internal import utils
13+
from tuf_conformance._internal.simulator_server import SimulatorServer
1414

1515

1616
class TestRepositorySimulator(unittest.TestCase):

tuf_conformance/test_rollback.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import pytest
22
from tuf.api.metadata import DelegatedRole, Root, Snapshot, Targets, Timestamp
33

4-
from tuf_conformance.client_runner import ClientRunner
5-
from tuf_conformance.simulator_server import SimulatorServer
4+
from tuf_conformance._internal.client_runner import ClientRunner
5+
from tuf_conformance._internal.simulator_server import SimulatorServer
66

77

88
def test_new_timestamp_version_rollback(
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

3-
from tuf_conformance.client_runner import ClientRunner
4-
from tuf_conformance.simulator_server import StaticServer
3+
from tuf_conformance._internal.client_runner import ClientRunner
4+
from tuf_conformance._internal.simulator_server import StaticServer
55

66

77
@pytest.mark.parametrize("static_repo", StaticServer.static_test_names())
@@ -13,8 +13,8 @@ def test_static_repository(
1313
This test is not a specification compliance test: It tests client compatibility
1414
with the repository format that a specific repository implementation produces.
1515
"""
16-
init_data, targetpath = static_server.new_test(static_repo)
16+
init_data, targetpath, refresh_time = static_server.new_test(static_repo)
1717

1818
assert static_client.init_client(init_data) == 0
19-
assert static_client.refresh(init_data) == 0
19+
assert static_client.refresh(init_data, refresh_time) == 0
2020
assert static_client.download_target(init_data, targetpath) == 0

tuf_conformance/test_updater_delegation_graphs.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
Timestamp,
1010
)
1111

12-
from tuf_conformance.client_runner import ClientRunner
13-
from tuf_conformance.repository_simulator import RepositorySimulator
14-
from tuf_conformance.simulator_server import SimulatorServer
12+
from tuf_conformance._internal.client_runner import ClientRunner
13+
from tuf_conformance._internal.repository_simulator import RepositorySimulator
14+
from tuf_conformance._internal.simulator_server import SimulatorServer
1515

1616

1717
@dataclass

tuf_conformance/test_updater_key_rotations.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import pytest
44
from tuf.api.metadata import Root, Snapshot, Targets, Timestamp
55

6-
from tuf_conformance.client_runner import ClientRunner
7-
from tuf_conformance.simulator_server import SimulatorServer
6+
from tuf_conformance._internal.client_runner import ClientRunner
7+
from tuf_conformance._internal.simulator_server import SimulatorServer
88

99

1010
@dataclass

0 commit comments

Comments
 (0)