From 9bb6f85c64974a6604b0a1d35d15a63dd6566619 Mon Sep 17 00:00:00 2001 From: Michal Domonkos Date: Thu, 2 May 2024 17:02:51 +0200 Subject: [PATCH] Add coverage for improved countme system age Adapt the countme feature to the libdnf fix for issue #1611, namely: - Turn the main scenario into a scenario outline to capture the various machine-id(5) configurations (see the table). - Add an upgrade scenario (from F39 to F40) that verifies that system age is now independent of $releasever on systems with a machine-id file. - Use NO_FAKE_STAT=1 in faketime invocations so that filesystem timestamps are *not* reported relative to the target time (this would break our custom machine-id timestamps we set here), see faketime(1) for details. - Add a new MachineId class to encapsulate the machine-id file, similar to OSRelease. - Mark the touched scenarios as destructive (due to them overriding the machine-id file). --- dnf-behave-tests/common/lib/cmd.py | 2 +- dnf-behave-tests/dnf/countme.feature | 82 +++++++++++++------ .../dnf/steps/fixtures/machineid.py | 63 ++++++++++++++ dnf-behave-tests/dnf/steps/repo.py | 17 ++++ 4 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 dnf-behave-tests/dnf/steps/fixtures/machineid.py diff --git a/dnf-behave-tests/common/lib/cmd.py b/dnf-behave-tests/common/lib/cmd.py index 52c4d25bb..6b0a7b6bf 100644 --- a/dnf-behave-tests/common/lib/cmd.py +++ b/dnf-behave-tests/common/lib/cmd.py @@ -31,7 +31,7 @@ def run(cmd, shell=True, cwd=None): def run_in_context(context, cmd, can_fail=False, expected_exit_code=None, **run_args): if getattr(context, "faketime", None) is not None: - cmd = context.faketime + cmd + cmd = 'NO_FAKE_STAT=1 ' + context.faketime + cmd if getattr(context, "fake_kernel_release", None) is not None: cmd = context.fake_kernel_release + cmd diff --git a/dnf-behave-tests/dnf/countme.feature b/dnf-behave-tests/dnf/countme.feature index 81daa4ace..a572d3e6f 100644 --- a/dnf-behave-tests/dnf/countme.feature +++ b/dnf-behave-tests/dnf/countme.feature @@ -40,64 +40,90 @@ Feature: Better user counting | header | value | | User-Agent | Agent 007 | - Scenario: Countme flag is sent once per calendar week - Given I set config option "countme" to "1" + @destructive + Scenario Outline: Countme flag is sent once per calendar week + Given the machine-id file is as of + And I set config option "countme" to "1" + And I set releasever to "39" And I copy repository "dnf-ci-fedora" for modification And I use repository "dnf-ci-fedora" as http And I set up metalink for repository "dnf-ci-fedora" And I start capturing outbound HTTP requests - # First calendar week (bucket 1) + # First calendar week # Note: One in the first 4 requests is randomly chosen to include the # flag (see COUNTME_BUDGET=4 in libdnf/repo/Repo.cpp for details) - When today is Wednesday, August 07, 2019 + When today is When I execute dnf with args "makecache" 4 times Then exactly one HTTP GET request should match: - | path | - | */metalink.xml*&countme=1 | + | path | + | */metalink.xml*&countme= | # Same calendar week (should not be sent) - When today is Friday, August 09, 2019 + When today is + 3 days And I forget any HTTP requests captured so far And I execute dnf with args "makecache" 4 times Then no HTTP GET request should match: - | path | - | */metalink.xml*&countme=* | + | path | + | */metalink.xml*&countme=* | - # Next calendar week (bucket 1) - When today is Tuesday, August 13, 2019 + # Next calendar week + When today is + 8 days And I forget any HTTP requests captured so far And I execute dnf with args "makecache" 4 times Then exactly one HTTP GET request should match: - | path | - | */metalink.xml*&countme=1 | + | path | + | */metalink.xml*&countme= | - # Next calendar week (bucket 2) - When today is Tuesday, August 21, 2019 + # Next calendar week + When today is + 15 days And I forget any HTTP requests captured so far And I execute dnf with args "makecache" 4 times Then exactly one HTTP GET request should match: - | path | - | */metalink.xml*&countme=2 | + | path | + | */metalink.xml*&countme= | - # 1 calendar month later (bucket 3) - When today is Tuesday, September 16, 2019 + # Next calendar month + When today is + 40 days And I forget any HTTP requests captured so far And I execute dnf with args "makecache" 4 times Then exactly one HTTP GET request should match: - | path | - | */metalink.xml*&countme=3 | + | path | + | */metalink.xml*&countme= | - # 6 calendar months later (bucket 4) - When today is Tuesday, March 15, 2020 + # 6 calendar months later + When today is + 182 days And I forget any HTTP requests captured so far And I execute dnf with args "makecache" 4 times Then exactly one HTTP GET request should match: - | path | - | */metalink.xml*&countme=4 | + | path | + | */metalink.xml*&countme= | + + # Even later, after a system upgrade + When today is + 365 days + And I set releasever to "40" + And I forget any HTTP requests captured so far + And I execute dnf with args "makecache" 4 times + Then exactly one HTTP GET request should match: + | path | + | */metalink.xml*&countme= | + Examples: + | status | epoch | date | age#1 | age#2 | age#3 | age#4 | age#5 | age#6 | + # Absolute age counting (since the epoch) + | initialized | Aug 06, 2019 | Aug 07, 2019 | 1 | 1 | 2 | 3 | 4 | 4 | + | initialized | Aug 06, 2019 | Aug 20, 2019 | 2 | 2 | 2 | 3 | 4 | 4 | + | initialized | Aug 06, 2019 | Sep 12, 2019 | 3 | 3 | 3 | 3 | 4 | 4 | + | initialized | Aug 06, 2019 | Jun 19, 2020 | 4 | 4 | 4 | 4 | 4 | 4 | + # Relative age counting (since the first request) + | uninitialized | Aug 06, 2019 | Jun 19, 2020 | 1 | 1 | 2 | 3 | 4 | 1 | + | empty | --- | Jun 19, 2020 | 1 | 1 | 2 | 3 | 4 | 1 | + | absent | --- | Jun 19, 2020 | 1 | 1 | 2 | 3 | 4 | 1 | + + @destructive Scenario: Countme flag is not sent repeatedly on retries - Given I set config option "countme" to "1" + Given the machine-id file is initialized as of today + And I set config option "countme" to "1" And I copy repository "dnf-ci-fedora" for modification And I use repository "dnf-ci-fedora" as http And I set up metalink for repository "dnf-ci-fedora" @@ -114,8 +140,10 @@ Feature: Better user counting | path | | */metalink.xml*&countme=1 | + @destructive Scenario: Countme feature is disabled - Given I set config option "countme" to "0" + Given the machine-id file is initialized as of today + And I set config option "countme" to "0" And I copy repository "dnf-ci-fedora" for modification And I use repository "dnf-ci-fedora" as http And I set up metalink for repository "dnf-ci-fedora" diff --git a/dnf-behave-tests/dnf/steps/fixtures/machineid.py b/dnf-behave-tests/dnf/steps/fixtures/machineid.py new file mode 100644 index 000000000..ade878ef3 --- /dev/null +++ b/dnf-behave-tests/dnf/steps/fixtures/machineid.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from __future__ import print_function + +from behave import fixture +from datetime import datetime +import os + + +class MachineId(object): + """Represents the machine-id(5) file.""" + def __init__(self, path): + self._path = path + self._backup = path + '.bak' + if os.path.exists(path): + os.rename(path, self._backup) + + def _set_mtime(self, value): + """Set the given mtime on this file.""" + times = None + if value is not None and value != 'today': + ts = int(datetime.strptime(value, '%b %d, %Y').timestamp()) + times = (ts, ts) + os.utime(self._path, times = times) + + def initialize(self, mtime): + """Initialize the file and set the given mtime.""" + with open(self._path, 'w') as f: + f.write('dummy\n') + self._set_mtime(mtime) + + def uninitialize(self, mtime): + """Uninitialize the file and set the given mtime.""" + with open(self._path, 'w') as f: + f.write('uninitialized\n') + self._set_mtime(mtime) + + def empty(self): + """Empty the file.""" + open(self._path, 'w').close() + + def delete(self): + """Delete the file.""" + if os.path.exists(self._path): + os.remove(self._path) + + def __del__(self): + """Restore the backup.""" + if os.path.exists(self._backup): + os.rename(self._backup, self._path) + + +@fixture +def machineid_fixture(context): + try: + if not hasattr(context, "machineid"): + path = os.path.realpath('/etc/machine-id') + context.scenario.machineid = MachineId(path) + + yield context.scenario.machineid + finally: + del context.scenario.machineid diff --git a/dnf-behave-tests/dnf/steps/repo.py b/dnf-behave-tests/dnf/steps/repo.py index b78b8dc69..083cd9cb7 100644 --- a/dnf-behave-tests/dnf/steps/repo.py +++ b/dnf-behave-tests/dnf/steps/repo.py @@ -23,6 +23,7 @@ ) from fixtures import start_server_based_on_type, stop_server_type from fixtures.osrelease import osrelease_fixture +from fixtures.machineid import machineid_fixture def repo_config(repo, new={}): @@ -381,6 +382,22 @@ def given_no_osrelease(context): context.scenario.osrelease.delete() +@behave.step("the machine-id file is {what} as of {when}") +def step_machine_id_file(context, what, when): + behave.use_fixture(machineid_fixture, context) + machineid = context.scenario.machineid + if when.startswith('on '): + when = when[3:] + if what == 'initialized': + machineid.initialize(when) + elif what == 'uninitialized': + machineid.uninitialize(when) + elif what == 'empty': + machineid.empty() + elif what == 'absent': + machineid.delete() + + @behave.step("I invalidate solvfile version of \"{path}\"") def rewrite_solvfile_version(context, path): path = path.format(context=context)