Skip to content

Commit

Permalink
Add coverage for improved countme system age
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
dmnks committed May 9, 2024
1 parent ec0cb2d commit ab365d2
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 28 deletions.
2 changes: 1 addition & 1 deletion dnf-behave-tests/common/lib/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
82 changes: 55 additions & 27 deletions dnf-behave-tests/dnf/countme.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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 <status> as of <epoch>
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 <date>
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=<age#1> |

# Same calendar week (should not be sent)
When today is Friday, August 09, 2019
When today is <date> + 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 <date> + 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=<age#2> |

# Next calendar week (bucket 2)
When today is Tuesday, August 21, 2019
# Next calendar week
When today is <date> + 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=<age#3> |

# 1 calendar month later (bucket 3)
When today is Tuesday, September 16, 2019
# Next calendar month
When today is <date> + 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=<age#4> |

# 6 calendar months later (bucket 4)
When today is Tuesday, March 15, 2020
# 6 calendar months later
When today is <date> + 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=<age#5> |

# Even later, after a system upgrade
When today is <date> + 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=<age#6> |

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"
Expand All @@ -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"
Expand Down
63 changes: 63 additions & 0 deletions dnf-behave-tests/dnf/steps/fixtures/machineid.py
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions dnf-behave-tests/dnf/steps/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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={}):
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit ab365d2

Please sign in to comment.