Skip to content

Commit

Permalink
add brew tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gerblesh committed Nov 24, 2024
1 parent 073f9f9 commit ad91a49
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 124 deletions.
97 changes: 1 addition & 96 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ on:
branches:
- main
workflow_dispatch:
env:
IMAGE_NAME: ublue-update
IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }}

jobs:
push-ghcr:
name: Build and push image
name: Build and test image
runs-on: ubuntu-24.04
permissions:
contents: read
Expand All @@ -34,47 +31,6 @@ jobs:
- name: Checkout Push to Registry action
uses: actions/checkout@v4

- name: Generate tags
id: generate-tags
shell: bash
run: |
# Generate a timestamp for creating an image version history
TIMESTAMP="$(date +%Y%m%d)"
MAJOR_VERSION="${{ matrix.major_version }}"
COMMIT_TAGS=()
BUILD_TAGS=()
# Have tags for tracking builds during pull request
SHA_SHORT="${GITHUB_SHA::7}"
COMMIT_TAGS+=("pr-${{ github.event.pull_request.number }}-${MAJOR_VERSION}")
COMMIT_TAGS+=("${SHA_SHORT}-${MAJOR_VERSION}")
if [[ "${{ matrix.is_latest_version }}" == "true" ]] && \
[[ "${{ matrix.is_stable_version }}" == "true" ]]; then
COMMIT_TAGS+=("pr-${{ github.event.pull_request.number }}")
COMMIT_TAGS+=("${SHA_SHORT}")
fi
BUILD_TAGS=("${MAJOR_VERSION}" "${MAJOR_VERSION}-${TIMESTAMP}")
if [[ "${{ matrix.is_latest_version }}" == "true" ]] && \
[[ "${{ matrix.is_stable_version }}" == "true" ]]; then
BUILD_TAGS+=("latest")
fi
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
echo "Generated the following commit tags: "
for TAG in "${COMMIT_TAGS[@]}"; do
echo "${TAG}"
done
alias_tags=("${COMMIT_TAGS[@]}")
else
alias_tags=("${BUILD_TAGS[@]}")
fi
echo "Generated the following build tags: "
for TAG in "${BUILD_TAGS[@]}"; do
echo "${TAG}"
done
echo "alias_tags=${alias_tags[*]}" >> $GITHUB_OUTPUT
- name: Install Deps
run: |
sudo apt-get install just podman
Expand All @@ -90,54 +46,3 @@ jobs:
id: test_image
run: |
just container-test
# Workaround bug where capital letters in your GitHub username make it impossible to push to GHCR.
# https://github.com/macbre/push-to-ghcr/issues/12
- name: Lowercase Registry
id: registry_case
uses: ASzc/change-string-case-action@v6
with:
string: ${{ env.IMAGE_REGISTRY }}

# Push the image to GHCR (Image Registry)
- name: Push To GHCR
uses: redhat-actions/push-to-registry@v2
id: push
if: github.event_name != 'pull_request'
env:
REGISTRY_USER: ${{ github.actor }}
REGISTRY_PASSWORD: ${{ github.token }}
with:
image: ${{ steps.build_image.outputs.image }}
tags: ${{ steps.build_image.outputs.tags }}
registry: ${{ steps.registry_case.outputs.lowercase }}
username: ${{ env.REGISTRY_USER }}
password: ${{ env.REGISTRY_PASSWORD }}
extra-args: |
--disable-content-trust
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Sign container
- uses: sigstore/[email protected]
if: github.event_name != 'pull_request'

- name: Sign container image
if: github.event_name != 'pull_request'
run: |
cosign sign -y --key env://COSIGN_PRIVATE_KEY ${{ steps.registry_case.outputs.lowercase }}/${{ env.IMAGE_NAME }}@${TAGS}
env:
TAGS: ${{ steps.push.outputs.digest }}
COSIGN_EXPERIMENTAL: false
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}

- name: Echo outputs
if: github.event_name != 'pull_request'
run: |
echo "${{ toJSON(steps.push.outputs) }}"
2 changes: 1 addition & 1 deletion src/ublue_update/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def inhibitor_checks_failed(
raise Exception(f"update failed to pass checks: \n - {exception_log}")


def run_updates(system, system_update_available, dry_run):
def run_updates(system: bool, system_update_available: bool, dry_run: bool):
process_uid = os.getuid()
filelock_path = "/run/ublue-update.lock"
if process_uid != 0:
Expand Down
5 changes: 2 additions & 3 deletions src/ublue_update/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
log = logging.getLogger(__name__)


def get_active_users():
def get_active_users() -> list:
out = subprocess.run(
[
"/usr/bin/busctl",
Expand All @@ -26,7 +26,7 @@ def get_active_users():
return users["data"][0]


def run_uid(uid: int, args):
def run_uid(uid: int, args: list[str]) -> subprocess.CompletedProcess[bytes]:
run_args = [
"/usr/bin/systemd-run",
"--user",
Expand All @@ -35,5 +35,4 @@ def run_uid(uid: int, args):
"--pipe",
"--quiet",
]

return subprocess.run(run_args + args, capture_output=True)
34 changes: 19 additions & 15 deletions src/ublue_update/update_drivers/brew.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,45 @@

log = logging.getLogger(__name__)

brew_prefix = "/home/linuxbrew/.linuxbrew"
brew_cellar = f"{brew_prefix}/Cellar"
brew_repo = f"{brew_prefix}/Homebrew"
BREW_PREFIX = "/home/linuxbrew/.linuxbrew"
BREW_CELLAR = f"{BREW_PREFIX}/Cellar"
BREW_REPO = f"{BREW_PREFIX}/Homebrew"


def detect_user():
if not os.path.isdir(brew_prefix):
def detect_user() -> int:
if not os.path.isdir(BREW_PREFIX):
return -1
return os.stat(brew_prefix).st_uid
return os.stat(BREW_PREFIX).st_uid


def brew_update(dry_run):
uid = detect_user()
def brew_update(dry_run: bool):
uid: int = detect_user()
if uid == -1 or dry_run:
return
log.info(f"running brew updates for uid: {uid}")
path = f"{os.environ["PATH"]}:{brew_prefix}/bin:{brew_prefix}/sbin"
args = [
f"--E=HOMEBREW_PREFIX='{brew_prefix}'",
f"--E=HOMEBREW_CELLAR='{brew_cellar}'",
f"--E=HOMEBREW_REPOSITORY='{brew_repo}'",
env_path: str = os.environ["PATH"]
path: str = f"{env_path}:{BREW_PREFIX}/bin:{BREW_PREFIX}/sbin"
args: list[str] = [
f"--E=HOMEBREW_PREFIX='{BREW_PREFIX}'",
f"--E=HOMEBREW_CELLAR='{BREW_CELLAR}'",
f"--E=HOMEBREW_REPOSITORY='{BREW_REPO}'",
f"--E=PATH='{path}'",
]
out = run_uid(args + ["brew", "update"])

out = run_uid(uid, args + ["brew", "update"])
if out.returncode != 0:
log.error(
f"brew update failed, returned code {out.returncode}, program output:"
)
log.error(out.stdout.decode("utf-8"))
return
out = run_uid(args + ["brew", "upgrade"])

out = run_uid(uid, args + ["brew", "upgrade"])
if out.returncode != 0:
log.error(
f"brew upgrade failed, returned code {out.returncode}, program output:"
)
log.error(out.stdout.decode("utf-8"))
return

log.info("brew updates completed")
94 changes: 94 additions & 0 deletions tests/unit/test_brew.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import os
import sys
from unittest.mock import patch

sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../src"))
)

from ublue_update.update_drivers.brew import detect_user, brew_update, BREW_PREFIX, BREW_CELLAR, BREW_REPO


@patch('os.path.isdir')
@patch('os.stat')
def test_detect_user_success(mock_stat, mock_isdir):
mock_isdir.return_value = True
mock_stat.return_value.st_uid = 1001

assert detect_user() == 1001

mock_isdir.assert_called_once_with(BREW_PREFIX)
mock_stat.assert_called_once_with(BREW_PREFIX)

@patch('os.path.isdir')
def test_detect_user_failure(mock_isdir):
mock_isdir.return_value = False

assert detect_user() == -1

mock_isdir.assert_called_once_with(BREW_PREFIX)

@patch('ublue_update.update_drivers.brew.run_uid')
@patch('os.environ', {'PATH': '/usr/bin'})
@patch('os.path.isdir')
@patch('os.stat')
@patch('ublue_update.update_drivers.brew.log')
def test_brew_update(mock_log, mock_stat, mock_isdir, mock_run_uid):
# Setup
mock_isdir.return_value = True
mock_stat.return_value.st_uid = 1001
mock_run_uid.return_value.returncode = 0 # Simulate a successful command

brew_update(True)

# Test that brew_update returns early when dry_run is True
mock_run_uid.assert_not_called()
mock_log.info.assert_not_called()

brew_update(False)
env = [
f"--E=HOMEBREW_PREFIX='{BREW_PREFIX}'",
f"--E=HOMEBREW_CELLAR='{BREW_CELLAR}'",
f"--E=HOMEBREW_REPOSITORY='{BREW_REPO}'",
f"--E=PATH='/usr/bin:{BREW_PREFIX}/bin:{BREW_PREFIX}/sbin'",
]

mock_run_uid.assert_any_call(1001, env + [
"brew", "update"
])
mock_run_uid.assert_any_call(1001, env + [
"brew", "upgrade"
])

@patch('ublue_update.update_drivers.brew.run_uid')
@patch('os.environ', {'PATH': '/usr/local/bin'})
@patch('os.path.isdir')
@patch('os.stat')
@patch('ublue_update.update_drivers.brew.log')
def test_brew_update_failure(mock_log, mock_stat, mock_isdir, mock_run_uid):
mock_isdir.return_value = True
mock_stat.return_value.st_uid = 1001
mock_run_uid.return_value.returncode = 1 # Simulate a failure in the `brew update` command
mock_run_uid.return_value.stdout = b"Error"

brew_update(False)

mock_log.error.assert_any_call("Error")

@patch('ublue_update.update_drivers.brew.run_uid')
@patch('os.environ', {'PATH': '/usr/local/bin'})
@patch('os.path.isdir')
@patch('os.stat')
@patch('ublue_update.update_drivers.brew.log')
def test_brew_update_upgrade_failure(mock_log, mock_stat, mock_isdir, mock_run_uid):
mock_isdir.return_value = True
mock_stat.return_value.st_uid = 1001
mock_run_uid.return_value.returncode = 0 # Simulate a successful `brew update`
mock_run_uid.return_value.stdout = b"Update complete"

mock_run_uid.return_value.returncode = 1 # Simulate a failure during `brew upgrade`
mock_run_uid.return_value.stdout = b'Upgrade error'

brew_update(False)

mock_log.error.assert_any_call('Upgrade error')
16 changes: 8 additions & 8 deletions tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def test_ask_for_updates_system(mock_run_updates, mock_notify, mock_cfg):
["universal-blue-update-confirm=Confirm"],
"critical",
)
mock_run_updates.assert_called_once_with(system, True)
mock_run_updates.assert_called_once_with(system, True, False)


@patch("ublue_update.cli.cfg")
Expand All @@ -98,7 +98,7 @@ def test_ask_for_updates_user(mock_run_updates, mock_notify, mock_cfg):
["universal-blue-update-confirm=Confirm"],
"critical",
)
mock_run_updates.assert_called_once_with(system, True)
mock_run_updates.assert_called_once_with(system, True, False)


def test_inhibitor_checks_failed():
Expand Down Expand Up @@ -129,7 +129,7 @@ def test_run_updates_user_in_progress(mock_acquire_lock, mock_os):
mock_os.path.isdir.return_value = True
mock_acquire_lock.return_value = None
with pytest.raises(Exception, match="updates are already running for this user"):
run_updates(False, True)
run_updates(False, True, False)


@patch("ublue_update.cli.os")
Expand All @@ -143,7 +143,7 @@ def test_run_updates_user_system(mock_transaction_wait, mock_acquire_lock, mock_
Exception,
match="ublue-update needs to be run as root to perform system updates!",
):
run_updates(True, True)
run_updates(True, True, False)


@patch("ublue_update.cli.os")
Expand All @@ -157,7 +157,7 @@ def test_run_updates_user_no_system(
mock_os.getuid.return_value = 1001
mock_acquire_lock.return_value = fd
mock_os.path.isdir.return_value = False
run_updates(False, True)
run_updates(False, True, False)
mock_release_lock.assert_called_once_with(fd)


Expand Down Expand Up @@ -190,7 +190,7 @@ def test_run_updates_system(
mock_run.return_value = output
mock_pending_deployment_check.return_value = True
mock_cfg.dbus_notify.return_value = True
run_updates(True, True)
run_updates(True, True, False)
mock_notify.assert_any_call(
"System Updater",
"System passed checks, updating ...",
Expand Down Expand Up @@ -240,7 +240,7 @@ def test_run_updates_without_image_update(
mock_pending_deployment_check.return_value = True
mock_cfg.dbus_notify.return_value = True
# System Update, but no Image Update Available
run_updates(True, False)
run_updates(True, False, False)
mock_notify.assert_not_called()
mock_run.assert_any_call(
[
Expand Down Expand Up @@ -284,7 +284,7 @@ def test_run_updates_system_reboot(
mock_cfg.dbus_notify.return_value = True
reboot = MagicMock(stdout=b"universal-blue-update-reboot")
mock_notify.side_effect = [None, reboot]
run_updates(True, True)
run_updates(True, True, False)
mock_notify.assert_any_call(
"System Updater",
"System passed checks, updating ...",
Expand Down
Loading

0 comments on commit ad91a49

Please sign in to comment.