From patchwork Fri Jul 12 12:13:24 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anton Mikanovich X-Patchwork-Id: 3685 Return-Path: Received: from shymkent.ilbers.de ([unix socket]) by shymkent (Cyrus 2.5.10-Debian-2.5.10-3+deb9u2) with LMTPA; Fri, 12 Jul 2024 14:13:47 +0200 X-Sieve: CMU Sieve 2.4 Received: from mail-lj1-f189.google.com (mail-lj1-f189.google.com [209.85.208.189]) by shymkent.ilbers.de (8.15.2/8.15.2/Debian-8+deb9u1) with ESMTPS id 46CCDike022876 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT) for ; Fri, 12 Jul 2024 14:13:44 +0200 Received: by mail-lj1-f189.google.com with SMTP id 38308e7fff4ca-2eebdfcb63esf20418141fa.2 for ; Fri, 12 Jul 2024 05:13:44 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1720786419; cv=pass; d=google.com; s=arc-20160816; b=orcbZHVI7/ZfrvvlqVIw/2i0df5kh5f4kQerg0d/WnsJKnZ+6Ne7M7zK0zV8h7pZy3 k+uTT1R8vIFzBXB8DT5qwQ50jbcnadzbsT053I9DeFtlrOJaaCnZCw1l7GkjVSWPsswv 0NWhDD0z4Q3XCGR7dO7Q+V4CKTW8vqE5erKMICGmF3+651SkLe9JdDYXHfFMIvQ6xnGI rtNCSW8Z1U5LNEWj7x8W4bpqw6505NJ+jK+veIVArG1+TEdDB4WjbB7Oy/5Fs6avKIU6 Hy2rHdm218XZfXZglIpal0TqNDUyn8ll5lEq72zJwar9octfQqpDdH+gjEWhnRqB+kOA yRTw== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:sender:dkim-signature; bh=kl+0nwHBwv16xFZusxV95ZZxXlXou6Jl8m1T3u2EAUU=; fh=JlH796MzfFaYcsd5Faokzi1hGNBsuNaPDXeWHRL2FGg=; b=VPy2jItOdGvG+msi5pjfZ1LqwdUV+Wa0Em0FhM9/FXG9cbnv37OeSJ0IttWBoT7wx6 GyTbsyefx/rU4S5xny9gGU/iWygH+2MpQp6OTggH1REwQH1Qj1F4XUgW9RNUQfjdH9Ty 7ow2G35awwCFYImdS3PSkaK9TBbDsay83NhqR8pEwisypUG1Jf7CWRSCfkjM96s2Ck3G XFgeu7Edvu4LZEhE8g7E4jwRS2qQlVb/1AIRJ/C4Z8NeHUVqom0ZnGa7cL+f/q17gAod 9m6unmqwULW71yvckfpjU+iQ0B40/UJr8txJ8+VAG8KsHwGTUy6rOoX9o1WacxJXAqXo 50iw==; darn=isar-build.org ARC-Authentication-Results: i=2; gmr-mx.google.com; spf=pass (google.com: domain of amikan@ilbers.de designates 85.214.156.166 as permitted sender) smtp.mailfrom=amikan@ilbers.de DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20230601; t=1720786419; x=1721391219; darn=isar-build.org; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-authentication-results :x-original-sender:mime-version:references:in-reply-to:message-id :date:subject:cc:to:from:sender:from:to:cc:subject:date:message-id :reply-to; bh=kl+0nwHBwv16xFZusxV95ZZxXlXou6Jl8m1T3u2EAUU=; b=cU7wz25TR271zBzBMMZ6oHdGu2Nl0XLT6XFasa1ghqY9O+d8xkRLO+2uks4gUwQxyp sz+WsKo/CbyoyM651z0ZwYIenV/EIRBpuiw7z8ti/oRL2qTmwchTmRz/LmtsWntUJi2G SAQgZco5kO2voGwct4aYFgqIeGIXWmXnrh4UCe9VzDv5tPZec3/lOng+tvpOOE/Ri9Oh mLL62MwVHU8lz79bnNRLC80X5Ev4G23fDdPs1ejufw+A40cmboXqRG3uGow9mZhWhTKT OvEs4i/4thEYqLfoXeQ6xjUk/QKgYbjxe/vlPz3gA+eVHtPEE4J1Madh3eikRj4HDz8D ZtiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1720786419; x=1721391219; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :x-spam-checked-in-group:list-id:mailing-list:precedence :x-original-authentication-results:x-original-sender:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :x-beenthere:x-gm-message-state:sender:from:to:cc:subject:date :message-id:reply-to; bh=kl+0nwHBwv16xFZusxV95ZZxXlXou6Jl8m1T3u2EAUU=; b=uUfwCPxixZ8ps0qrvQYHVOPoHWxK7JqPKGjK1sOtOMS2Db0/fpqJf3ldrBqP+Q8OHl 7+CIuxt6YBD5Kmerb6LRcfMTo5/3qaU7Yu65Hz7WBvTD23rIUQOT9VSFYq9Ylal+yEkY 3/Snxx7rz/TjAGnhGS5Og4UXaTEBDbiRGDRoV+Ypq6LJcOgNAk6Y/WQRFBYsO6IRhL7y XvOX3nkrecGUJ6G7LLx726jWT6aFfskXuRWcnkUCxvNAKtOaOua9eCGWNC+1NwFzer35 xcxTNvQ5AwL/QEzFus6y6eDgK27nIEeYLEHkMC7KoxTr2mLT1c/MH0FfsKXpzc2P5c7E /oKQ== Sender: isar-users@googlegroups.com X-Forwarded-Encrypted: i=2; AJvYcCXcC83rP/25q3khenuvUf+whfVHy3OE/zXpEe/eVx3cSyo6fiZu5XBBtu53rN3/Rxdn8CliQpXdBemimWtb7cxGEPGZTDE= X-Gm-Message-State: AOJu0Yxh4VuWsEzs4vsBWuy6UR/2EPkM3+aRPdoKRX3QF22rdPPUEFqB /Wdvft8VdK1awhD6wp3IducZ9ENaP3uaENc0QMo62D51iS6wmXqy X-Google-Smtp-Source: AGHT+IEamUcQObJtbV8Ouc/1UnuNdOLhSwS0vNNY542lJOexrB3t+fkksit2QglbugyMF/vywm3nKg== X-Received: by 2002:a2e:8297:0:b0:2ec:53ad:457 with SMTP id 38308e7fff4ca-2eeb31716ecmr79543511fa.36.1720786418372; Fri, 12 Jul 2024 05:13:38 -0700 (PDT) X-BeenThere: isar-users@googlegroups.com Received: by 2002:a2e:a481:0:b0:2ec:4d9a:63bc with SMTP id 38308e7fff4ca-2eec93a6e39ls1437271fa.1.-pod-prod-09-eu; Fri, 12 Jul 2024 05:13:36 -0700 (PDT) X-Received: by 2002:a2e:9ed4:0:b0:2ec:5759:bf2f with SMTP id 38308e7fff4ca-2eeb30bc32emr75160671fa.7.1720786416189; Fri, 12 Jul 2024 05:13:36 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1720786416; cv=none; d=google.com; s=arc-20160816; b=JZhJUHNuAS/0pGwILs2xCNsDePKOknAanwDvAp3t+llmSJTXn6ciWuHhWGyz5NfbaQ AaVbfXpJe3pi5h5dNgEBbHsE3Ed6ZgKhRlnJXPhykz6+VqFxHqgRId4wSeNz1loK6ACE Ymg+JlxvbhFZ6+9RRA4DxoVXrR6k+xFKYv+EsEnrGL2dCsxqcGgqnUIggRawQKX5AUJi 0BoOdkAf2GeFJLEn1D7nWiJtysaRUm0WozG4UO0acyHO2GlxjiuRuSVnrxv8Hmgo2BfH Ur2Gf+z1aVsb8ellucFYGO482hhUCzWg7xwUElzouHu5ypqWud3Z87mfnMM/LBBB1LLX mzLA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from; bh=K2sXqvBHUMC0DPeP86JqlaJSO2JeI2vfRwv1sPTYnvM=; fh=2SRX+vvRah15LtEwg2fYq6jFTZih6AetHUWyVclC92U=; b=avtXTRJA+6Y5njwywLTC6gjQ48VND34UuvC5r8LlQyZ304p0elKBiuv4aKUrGB1JBP PReiJo6sW+m7IIkdiJegISWXS+YKf11W/a1UkCECtVunnVJYifEQv3Q/mMDoYVMoGivk suDDN7dBm/cZsXAIdARKGm8M79HwILm8RalzDhz5rjl0MONtGCITOR+UIO83sDkI+McY g8l92adDNgYC8pf5qT+PI0S6xtIQ68NupzIJGP67TpOrgf45mqbOVTrWvWPChkYf/cYH nrFOYTIfLyvLqbNZoM4YL3xBGkNWtyw/zDyjTptJu2NIeeA2U7hKHBK/f2p2eyoh0y2A KGwA==; dara=google.com ARC-Authentication-Results: i=1; gmr-mx.google.com; spf=pass (google.com: domain of amikan@ilbers.de designates 85.214.156.166 as permitted sender) smtp.mailfrom=amikan@ilbers.de Received: from shymkent.ilbers.de (shymkent.ilbers.de. [85.214.156.166]) by gmr-mx.google.com with ESMTPS id 5b1f17b1804b1-426740d995asi5064915e9.1.2024.07.12.05.13.36 for (version=TLS1_2 cipher=ECDHE-ECDSA-CHACHA20-POLY1305 bits=256/256); Fri, 12 Jul 2024 05:13:36 -0700 (PDT) Received-SPF: pass (google.com: domain of amikan@ilbers.de designates 85.214.156.166 as permitted sender) client-ip=85.214.156.166; Received: from user-B660.promwad.corp ([159.148.83.114]) (authenticated bits=0) by shymkent.ilbers.de (8.15.2/8.15.2/Debian-8+deb9u1) with ESMTPSA id 46CCDVoa022837 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 12 Jul 2024 14:13:34 +0200 From: Anton Mikanovich To: isar-users@googlegroups.com Cc: Anton Mikanovich , Ilia Skochilov Subject: [PATCH 2/2] testsuite: Fix code style Date: Fri, 12 Jul 2024 15:13:24 +0300 Message-Id: <20240712121324.2200037-3-amikan@ilbers.de> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240712121324.2200037-1-amikan@ilbers.de> References: <20240712121324.2200037-1-amikan@ilbers.de> MIME-Version: 1.0 X-Spam-Status: No, score=-4.6 required=5.0 tests=DKIMWL_WL_MED,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_EF,HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RCVD_IN_MSPIKE_H2,RCVD_IN_RP_CERTIFIED, RCVD_IN_RP_RNBL,RCVD_IN_RP_SAFE,SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on shymkent.ilbers.de X-Original-Sender: amikan@ilbers.de X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com: domain of amikan@ilbers.de designates 85.214.156.166 as permitted sender) smtp.mailfrom=amikan@ilbers.de Precedence: list Mailing-list: list isar-users@googlegroups.com; contact isar-users+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: isar-users@googlegroups.com X-Google-Group-Id: 914930254986 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= Bring the Python code into compliance with PEP8 requirements. Also change string quotes style for consistency throughout the code. Rebuild line wrapping and function/classes declaration spacing to be compliant with the current rules described in testsuite/README.md. Used black v23.1.0 and flake8 v5.0.4. Signed-off-by: Anton Mikanovich Signed-off-by: Ilia Skochilov --- testsuite/cibase.py | 237 ++++++---- testsuite/cibuilder.py | 425 +++++++++++------- testsuite/citest.py | 245 ++++++---- testsuite/repro-build-test.py | 39 +- testsuite/start_vm.py | 152 +++++-- testsuite/unittests/bitbake.py | 22 +- testsuite/unittests/rootfs.py | 9 +- .../unittests/test_image_account_extension.py | 162 ++++--- 8 files changed, 816 insertions(+), 475 deletions(-) diff --git a/testsuite/cibase.py b/testsuite/cibase.py index b2a804b7..cccac86c 100755 --- a/testsuite/cibase.py +++ b/testsuite/cibase.py @@ -5,18 +5,18 @@ import os import re import shutil import tempfile -import time from cibuilder import CIBuilder, isar_root from utils import CIUtils from avocado.utils import process + class CIBaseTest(CIBuilder): def perform_build_test(self, targets, **kwargs): self.configure(**kwargs) - self.log.info('Starting build...') + self.log.info("Starting build...") self.bitbake(targets, **kwargs) @@ -24,31 +24,43 @@ class CIBaseTest(CIBuilder): self.configure(wic_deploy_parts=wic_deploy_parts, **kwargs) self.bitbake(targets, **kwargs) - partition_files = set(glob.glob(f'{self.build_dir}/tmp/deploy/images/*/*.wic.p1')) + wic_path = f"{self.build_dir}/tmp/deploy/images/*/*.wic.p1" + partition_files = set(glob.glob(wic_path)) if wic_deploy_parts and len(partition_files) == 0: - self.fail('Found raw wic partitions in DEPLOY_DIR') + self.fail("Found raw wic partitions in DEPLOY_DIR") if not wic_deploy_parts and len(partition_files) != 0: - self.fail('Did not find raw wic partitions in DEPLOY_DIR') + self.fail("Did not find raw wic partitions in DEPLOY_DIR") def perform_repro_test(self, targets, signed=False, **kwargs): - gpg_pub_key = os.path.dirname(__file__) + '/keys/base-apt/test_pub.key' - gpg_priv_key = os.path.dirname(__file__) + '/keys/base-apt/test_priv.key' + keys_dir = os.path.dirname(__file__) + '/keys/base-apt' + gpg_pub_key = os.path.join(keys_dir, 'test_pub.key') + gpg_priv_key = os.path.join(keys_dir, 'test_priv.key') - self.configure(gpg_pub_key=gpg_pub_key if signed else None, sstate_dir="", **kwargs) + self.configure( + gpg_pub_key=gpg_pub_key if signed else None, + sstate_dir='', + **kwargs, + ) os.chdir(self.build_dir) os.environ['GNUPGHOME'] = gnupg_home = tempfile.mkdtemp() - result = process.run('gpg --import %s %s' % (gpg_pub_key, gpg_priv_key)) + result = process.run(f"gpg --import {gpg_pub_key} {gpg_priv_key}") if result.exit_status: - self.fail('GPG import failed') + self.fail("GPG import failed") try: self.bitbake(targets, **kwargs) - self.move_in_build_dir('tmp', 'tmp_middle_repro_%s' % ('signed' if signed else 'unsigned')) - self.configure(gpg_pub_key=gpg_pub_key if signed else None, offline=True, sstate_dir="", **kwargs) + repro_type = 'signed' if signed else 'unsigned' + self.move_in_build_dir('tmp', f"tmp_middle_repro_{repro_type}") + self.configure( + gpg_pub_key=gpg_pub_key if signed else None, + offline=True, + sstate_dir='', + **kwargs, + ) self.bitbake(targets, **kwargs) @@ -71,13 +83,13 @@ class CIBaseTest(CIBuilder): count = 0 for filename in glob.iglob(dir + '/**/stats', recursive=True): if os.path.isfile(filename): - with open(filename,'r') as file: + with open(filename, 'r') as file: content = file.readlines() - if (field < len(content)): + if field < len(content): count += int(content[field]) return count - self.configure(ccache=True, sstate_dir="", **kwargs) + self.configure(ccache=True, sstate_dir='', **kwargs) # Field that stores direct ccache hits direct_cache_hit = 22 @@ -86,21 +98,21 @@ class CIBaseTest(CIBuilder): self.delete_from_build_dir('sstate-cache') self.delete_from_build_dir('ccache') - self.log.info('Starting build and filling ccache dir...') + self.log.info("Starting build and filling ccache dir...") self.bitbake(targets, **kwargs) hit1 = ccache_stats(self.build_dir + '/ccache', direct_cache_hit) - self.log.info('Ccache hits 1: ' + str(hit1)) + self.log.info(f"Ccache hits 1: {str(hit1)}") self.move_in_build_dir('tmp', 'tmp_middle_ccache') self.delete_from_build_dir('sstate-cache') - self.log.info('Starting build and using ccache dir...') + self.log.info("Starting build and using ccache dir...") self.bitbake(targets, **kwargs) hit2 = ccache_stats(self.build_dir + '/ccache', direct_cache_hit) - self.log.info('Ccache hits 2: ' + str(hit2)) + self.log.info(f"Ccache hits 2: {str(hit2)}") if hit2 <= hit1: - self.fail('Ccache was not used on second build') + self.fail("Ccache was not used on second build") # Cleanup self.move_in_build_dir('tmp', 'tmp_after_ccache') @@ -112,10 +124,10 @@ class CIBaseTest(CIBuilder): # Use a different isar root for populating sstate cache isar_sstate = f"{isar_root}/isar-sstate" os.makedirs(isar_sstate) - process.run(f'git --work-tree={isar_sstate} checkout HEAD -- .') + process.run(f"git --work-tree={isar_sstate} checkout HEAD -- .") self.init('../build-sstate', isar_dir=isar_sstate) - self.configure(sstate=True, sstate_dir="", **kwargs) + self.configure(sstate=True, sstate_dir='', **kwargs) # Cleanup sstate and tmp before test self.delete_from_build_dir('sstate-cache') @@ -127,17 +139,30 @@ class CIBaseTest(CIBuilder): # Remove isar configuration so the the following test creates a new one self.delete_from_build_dir('conf') - def perform_signature_lint(self, targets, verbose=False, sources_dir=isar_root, - excluded_tasks=None, **kwargs): - """Generate signature data for target(s) and check for cachability issues.""" + def perform_signature_lint( + self, + targets, + verbose=False, + sources_dir=isar_root, + excluded_tasks=None, + **kwargs, + ): + """ + Generate signature data for target(s) and check for cachability issues + """ self.configure(**kwargs) - self.move_in_build_dir("tmp", "tmp_before_sstate") - self.bitbake(targets, sig_handler="none") - - verbose_arg = "--verbose" if verbose else "" - excluded_arg = f"--excluded-tasks {','.join(excluded_tasks)}" if excluded_tasks else "" - cmd = f"{isar_root}/scripts/isar-sstate lint --lint-stamps {self.build_dir}/tmp/stamps " \ - f"--build-dir {self.build_dir} --sources-dir {sources_dir} {verbose_arg} {excluded_arg}" + self.move_in_build_dir('tmp', 'tmp_before_sstate') + self.bitbake(targets, sig_handler='none') + + verbose_arg = '--verbose' if verbose else '' + excluded_arg = '' + if excluded_tasks: + excluded_arg = f"--excluded-tasks {','.join(excluded_tasks)}" + cmd = ( + f"{isar_root}/scripts/isar-sstate lint --lint-stamps " + f"{self.build_dir}/tmp/stamps --build-dir {self.build_dir} " + f"--sources-dir {sources_dir} {verbose_arg} {excluded_arg}" + ) self.log.info(f"Running: {cmd}") exit_status, output = process.getstatusoutput(cmd, ignore_status=True) if exit_status > 0: @@ -148,10 +173,11 @@ class CIBaseTest(CIBuilder): def perform_sstate_test(self, image_target, package_target, **kwargs): def check_executed_tasks(target, expected): - taskorder_file = glob.glob(f'{self.build_dir}/tmp/work/*/{target}/*/temp/log.task_order') + recipe_workdir = f"{self.build_dir}/tmp/work/*/{target}/*" + taskorder_file = glob.glob(f"{recipe_workdir}/temp/log.task_order") try: with open(taskorder_file[0], 'r') as f: - tasks = [l.split()[1] for l in f.readlines()] + tasks = [line.split()[1] for line in f.readlines()] except (FileNotFoundError, IndexError): tasks = [] if expected is None: @@ -163,75 +189,116 @@ class CIBaseTest(CIBuilder): should_run = False e = e[1:] if should_run != (e in tasks): - self.log.error(f"{target}: executed tasks {str(tasks)} did not match expected {str(expected)}") + self.log.error( + f"{target}: executed tasks {str(tasks)} did not match " + f"expected {str(expected)}" + ) return False return True - self.configure(sstate=True, sstate_dir="", **kwargs) + self.configure(sstate=True, sstate_dir='', **kwargs) + + deploy_dir = f"{self.build_dir}/tmp/deploy" - # Check signature files for cachability issues like absolute paths in signatures - result = process.run(f'{isar_root}/scripts/isar-sstate lint {self.build_dir}/sstate-cache ' - f'--build-dir {self.build_dir} --sources-dir {isar_root}') + # Check signature files for cachability issues like absolute paths in + # signatures + result = process.run( + f"{isar_root}/scripts/isar-sstate lint " + f"{self.build_dir}/sstate-cache --build-dir {self.build_dir} " + f"--sources-dir {isar_root}" + ) if result.exit_status > 0: self.fail("Detected cachability issues") # Save contents of image deploy dir - expected_files = set(glob.glob(f'{self.build_dir}/tmp/deploy/images/*/*')) + expected_files = set(glob.glob(f"{deploy_dir}/images/*/*")) # Rebuild image self.move_in_build_dir('tmp', 'tmp_before_sstate') self.bitbake(image_target, **kwargs) - if not all([ - check_executed_tasks('isar-bootstrap-target', - ['do_bootstrap_setscene', '!do_bootstrap']), - check_executed_tasks('sbuild-chroot-target', - ['do_rootfs_install_setscene', '!do_rootfs_install']), - check_executed_tasks('isar-image-base-*', - ['do_rootfs_install_setscene', '!do_rootfs_install']) - ]): + if not all( + [ + check_executed_tasks( + 'isar-bootstrap-target', + ['do_bootstrap_setscene', '!do_bootstrap'], + ), + check_executed_tasks( + 'sbuild-chroot-target', + ['do_rootfs_install_setscene', '!do_rootfs_install'], + ), + check_executed_tasks( + 'isar-image-base-*', + ['do_rootfs_install_setscene', '!do_rootfs_install'], + ), + ] + ): self.fail("Failed rebuild image") # Verify content of image deploy dir - deployed_files = set(glob.glob(f'{self.build_dir}/tmp/deploy/images/*/*')) + deployed_files = set(glob.glob(f"{deploy_dir}/images/*/*")) if not deployed_files == expected_files: if len(expected_files - deployed_files) > 0: - self.log.error(f"{target}: files missing from deploy dir after rebuild with sstate cache:" - f"{expected_files - deployed_files}") + self.log.error( + f"{image_target}: files missing from deploy dir after " + f"rebuild with sstate cache:" + f"{expected_files - deployed_files}" + ) if len(deployed_files - expected_files) > 0: - self.log.error(f"{target}: additional files in deploy dir after rebuild with sstate cache:" - f"{deployed_files - expected_files}") + self.log.error( + f"{image_target}: additional files in deploy dir after " + f"rebuild with sstate cache:" + f"{deployed_files - expected_files}" + ) self.fail("Failed rebuild image") # Rebuild single package self.move_in_build_dir('tmp', 'tmp_middle_sstate') self.bitbake(package_target, **kwargs) - if not all([ - check_executed_tasks('isar-bootstrap-target', - ['do_bootstrap_setscene']), - check_executed_tasks('sbuild-chroot-target', - ['!do_sbuildchroot_deploy']), - check_executed_tasks('hello', - ['do_dpkg_build_setscene', 'do_deploy_deb', '!do_dpkg_build']) - ]): + if not all( + [ + check_executed_tasks( + 'isar-bootstrap-target', ['do_bootstrap_setscene'] + ), + check_executed_tasks( + 'sbuild-chroot-target', ['!do_sbuildchroot_deploy'] + ), + check_executed_tasks( + 'hello', + [ + 'do_dpkg_build_setscene', + 'do_deploy_deb', + '!do_dpkg_build', + ], + ), + ] + ): self.fail("Failed rebuild single package") # Rebuild package and image self.move_in_build_dir('tmp', 'tmp_middle2_sstate') - process.run(f'find {self.build_dir}/sstate-cache/ -name sstate:hello:* -delete') + sstate_cache_dir = f"{self.build_dir}/sstate-cache/" + process.run(f"find {sstate_cache_dir} -name sstate:hello:* -delete") self.bitbake(image_target, **kwargs) - if not all([ - check_executed_tasks('isar-bootstrap-target', - ['do_bootstrap_setscene', '!do_bootstrap']), - check_executed_tasks('sbuild-chroot-target', - ['do_rootfs_install_setscene', '!do_rootfs_install']), - check_executed_tasks('hello', - ['do_fetch', 'do_dpkg_build']), - # TODO: if we actually make a change to hello, then we could test - # that do_rootfs is executed. currently, hello is rebuilt, - # but its sstate sig/hash does not change. - check_executed_tasks('isar-image-base-*', - ['do_rootfs_install_setscene', '!do_rootfs_install']) - ]): + if not all( + [ + check_executed_tasks( + 'isar-bootstrap-target', + ['do_bootstrap_setscene', '!do_bootstrap'], + ), + check_executed_tasks( + 'sbuild-chroot-target', + ['do_rootfs_install_setscene', '!do_rootfs_install'], + ), + check_executed_tasks('hello', ['do_fetch', 'do_dpkg_build']), + # TODO: if we actually make a change to hello, then we could + # test that do_rootfs is executed. currently, hello is + # rebuilt, but its sstate sig/hash does not change. + check_executed_tasks( + 'isar-image-base-*', + ['do_rootfs_install_setscene', '!do_rootfs_install'], + ), + ] + ): self.fail("Failed rebuild package and image") def perform_source_test(self, targets, **kwargs): @@ -242,9 +309,9 @@ class CIBaseTest(CIBuilder): package = target.rsplit(':', 1)[-1] isar_apt = CIUtils.getVars('REPO_ISAR_DB_DIR', target=target) fpath = f"{package}/{package}*.tar.*" - targz = set(glob.glob(f'{isar_apt}/../apt/*/pool/*/*/{fpath}')) + targz = set(glob.glob(f"{isar_apt}/../apt/*/pool/*/*/{fpath}")) if len(targz) < 1: - self.fail('No source packages found') + self.fail("No source packages found") for fname in targz: sfiles[target][fname] = CIUtils.get_tar_content(fname) return sfiles @@ -260,14 +327,16 @@ class CIBaseTest(CIBuilder): for filename in sfiles_before[tdir]: for file in sfiles_before[tdir][filename]: if os.path.basename(file).startswith('.git'): - self.fail('Found .git files') + self.fail("Found .git files") package = targets[0].rsplit(':', 1)[-1] - tmp_layer_nested_dirs = os.path.join(tmp_layer_dir, - 'recipes-app', package) + tmp_layer_nested_dirs = os.path.join( + tmp_layer_dir, 'recipes-app', package + ) os.makedirs(tmp_layer_nested_dirs, exist_ok=True) - bbappend_file = os.path.join(tmp_layer_nested_dirs, - package + '.bbappend') + bbappend_file = os.path.join( + tmp_layer_nested_dirs, package + '.bbappend' + ) with open(bbappend_file, 'w') as file: file.write('DPKG_SOURCE_EXTRA_ARGS = ""') @@ -278,12 +347,12 @@ class CIBaseTest(CIBuilder): for tdir in sfiles_after: for filename in sfiles_after[tdir]: if not sfiles_before[tdir][filename]: - self.fail('Source filenames are different') + self.fail("Source filenames are different") diff = [] for file in sfiles_after[tdir][filename]: if file not in sfiles_before[tdir][filename]: diff.append(file) if len(diff) < 1: - self.fail('Source packages are equal') + self.fail("Source packages are equal") finally: self.cleanup_tmp_layer(tmp_layer_dir) diff --git a/testsuite/cibuilder.py b/testsuite/cibuilder.py index a20e88f9..4731bf69 100755 --- a/testsuite/cibuilder.py +++ b/testsuite/cibuilder.py @@ -19,7 +19,7 @@ from avocado import Test from avocado.utils import path from avocado.utils import process -sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/../bitbake/lib') +sys.path.append(os.path.join(os.path.dirname(__file__), '../bitbake/lib')) import bb @@ -28,19 +28,23 @@ DEF_VM_TO_SEC = 600 isar_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) backup_prefix = '.ci-backup' -app_log = logging.getLogger("avocado.app") +app_log = logging.getLogger('avocado.app') + class CanBeFinished(Exception): pass + class CIBuilder(Test): def setUp(self): super(CIBuilder, self).setUp() job_log = os.path.join(os.path.dirname(self.logdir), '..', 'job.log') self._file_handler = logging.FileHandler(filename=job_log) self._file_handler.setLevel(logging.ERROR) - fmt = ('%(asctime)s %(module)-16.16s L%(lineno)-.4d %(' - 'levelname)-5.5s| %(message)s') + fmt = ( + '%(asctime)s %(module)-16.16s L%(lineno)-.4d ' + '%(levelname)-5.5s| %(message)s' + ) formatter = logging.Formatter(fmt=fmt) self._file_handler.setFormatter(formatter) app_log.addHandler(self._file_handler) @@ -49,22 +53,31 @@ class CIBuilder(Test): # initialize build_dir and setup environment # needs to run once (per test case) if hasattr(self, 'build_dir'): - self.error("Broken test implementation: init() called multiple times.") + self.error( + "Broken test implementation: init() called multiple times." + ) self.build_dir = os.path.join(isar_dir, build_dir) os.chdir(isar_dir) - os.environ["TEMPLATECONF"] = "meta-test/conf" + os.environ['TEMPLATECONF'] = 'meta-test/conf' path.usable_rw_dir(self.build_dir) - output = process.getoutput('/bin/bash -c "source isar-init-build-env \ - %s 2>&1 >/dev/null; env"' % self.build_dir) - env = dict(((x.split('=', 1) + [''])[:2] \ - for x in output.splitlines() if x != '')) + output = process.getoutput( + f"/bin/bash -c 'source isar-init-build-env {self.build_dir} 2>&1 " + f">/dev/null; env'" + ) + env = dict( + ( + (x.split('=', 1) + [''])[:2] + for x in output.splitlines() + if x != '' + ) + ) os.environ.update(env) self.vm_dict = {} self.vm_dict_file = '%s/vm_dict_file' % self.build_dir if os.path.isfile(self.vm_dict_file): - with open(self.vm_dict_file, "rb") as f: + with open(self.vm_dict_file, 'rb') as f: data = f.read() if data: self.vm_dict = pickle.loads(data) @@ -73,12 +86,25 @@ class CIBuilder(Test): if not hasattr(self, 'build_dir'): self.error("Broken test implementation: need to call init().") - def configure(self, compat_arch=True, cross=True, debsrc_cache=False, - container=False, ccache=False, sstate=False, offline=False, - gpg_pub_key=None, wic_deploy_parts=False, dl_dir=None, - sstate_dir=None, ccache_dir=None, - source_date_epoch=None, use_apt_snapshot=False, - image_install=None, **kwargs): + def configure( + self, + compat_arch=True, + cross=True, + debsrc_cache=False, + container=False, + ccache=False, + sstate=False, + offline=False, + gpg_pub_key=None, + wic_deploy_parts=False, + dl_dir=None, + sstate_dir=None, + ccache_dir=None, + source_date_epoch=None, + use_apt_snapshot=False, + image_install=None, + **kwargs, + ): # write configuration file and set bitbake_args # can run multiple times per test case self.check_init() @@ -104,24 +130,26 @@ class CIBuilder(Test): # get parameters from environment distro_apt_premir = os.getenv('DISTRO_APT_PREMIRRORS') - self.log.info(f'===================================================\n' - f'Configuring build_dir {self.build_dir}\n' - f' compat_arch = {compat_arch}\n' - f' cross = {cross}\n' - f' debsrc_cache = {debsrc_cache}\n' - f' offline = {offline}\n' - f' container = {container}\n' - f' ccache = {ccache}\n' - f' sstate = {sstate}\n' - f' gpg_pub_key = {gpg_pub_key}\n' - f' wic_deploy_parts = {wic_deploy_parts}\n' - f' source_date_epoch = {source_date_epoch} \n' - f' use_apt_snapshot = {use_apt_snapshot} \n' - f' dl_dir = {dl_dir}\n' - f' sstate_dir = {sstate_dir}\n' - f' ccache_dir = {ccache_dir}\n' - f' image_install = {image_install}\n' - f'===================================================') + self.log.info( + f"===================================================\n" + f"Configuring build_dir {self.build_dir}\n" + f" compat_arch = {compat_arch}\n" + f" cross = {cross}\n" + f" debsrc_cache = {debsrc_cache}\n" + f" offline = {offline}\n" + f" container = {container}\n" + f" ccache = {ccache}\n" + f" sstate = {sstate}\n" + f" gpg_pub_key = {gpg_pub_key}\n" + f" wic_deploy_parts = {wic_deploy_parts}\n" + f" source_date_epoch = {source_date_epoch} \n" + f" use_apt_snapshot = {use_apt_snapshot} \n" + f" dl_dir = {dl_dir}\n" + f" sstate_dir = {sstate_dir}\n" + f" ccache_dir = {ccache_dir}\n" + f" image_install = {image_install}\n" + f"===================================================" + ) # determine bitbake_args self.bitbake_args = [] @@ -142,7 +170,10 @@ class CIBuilder(Test): f.write('IMAGE_INSTALL += "kselftest"\n') if cross: f.write('ISAR_CROSS_COMPILE = "1"\n') - f.write('IMAGE_INSTALL:append:hikey = " linux-headers-${KERNEL_NAME}"\n') + f.write( + 'IMAGE_INSTALL:append:hikey = ' + '" linux-headers-${KERNEL_NAME}"\n' + ) if debsrc_cache: f.write('BASE_REPO_FEATURES = "cache-deb-src"\n') if offline: @@ -150,7 +181,10 @@ class CIBuilder(Test): f.write('BB_NO_NETWORK = "1"\n') if container: f.write('SDK_FORMATS = "docker-archive"\n') - f.write('IMAGE_INSTALL:remove = "example-module-${KERNEL_NAME} enable-fsck"\n') + f.write( + 'IMAGE_INSTALL:remove = ' + '"example-module-${KERNEL_NAME} enable-fsck"\n' + ) if gpg_pub_key: f.write('BASE_REPO_KEY="file://' + gpg_pub_key + '"\n') if wic_deploy_parts: @@ -161,7 +195,9 @@ class CIBuilder(Test): f.write('USE_CCACHE = "1"\n') f.write('CCACHE_TOP_DIR = "%s"\n' % ccache_dir) if source_date_epoch: - f.write('SOURCE_DATE_EPOCH_FALLBACK = "%s"\n' % source_date_epoch) + f.write( + 'SOURCE_DATE_EPOCH_FALLBACK = "%s"\n' % source_date_epoch + ) if use_apt_snapshot: f.write('ISAR_USE_APT_SNAPSHOT = "1"\n') if dl_dir: @@ -194,9 +230,9 @@ class CIBuilder(Test): def bitbake(self, target, bitbake_cmd=None, sig_handler=None, **kwargs): self.check_init() - self.log.info('===================================================') - self.log.info('Building ' + str(target)) - self.log.info('===================================================') + self.log.info("===================================================") + self.log.info(f"Building {str(target)}") + self.log.info("===================================================") os.chdir(self.build_dir) cmdline = ['bitbake'] if self.bitbake_args: @@ -212,9 +248,13 @@ class CIBuilder(Test): else: cmdline.append(target) - with subprocess.Popen(" ".join(cmdline), stdout=subprocess.PIPE, - stderr=subprocess.PIPE, universal_newlines=True, - shell=True) as p1: + with subprocess.Popen( + ' '.join(cmdline), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + shell=True, + ) as p1: poller = select.poll() poller.register(p1.stdout, select.POLLIN) poller.register(p1.stderr, select.POLLIN) @@ -229,28 +269,28 @@ class CIBuilder(Test): app_log.error(p1.stderr.readline().rstrip()) p1.wait() if p1.returncode: - self.fail('Bitbake failed') + self.fail("Bitbake failed") def backupfile(self, path): self.check_init() try: shutil.copy2(path, path + backup_prefix) except FileNotFoundError: - self.log.warn(path + ' not exist') + self.log.warn(f"{path} not exist") def backupmove(self, path): self.check_init() try: shutil.move(path, path + backup_prefix) except FileNotFoundError: - self.log.warn(path + ' not exist') + self.log.warn(f"{path} not exist") def restorefile(self, path): self.check_init() try: shutil.move(path + backup_prefix, path) except FileNotFoundError: - self.log.warn(path + backup_prefix + ' not exist') + self.log.warn(f"{path}{backup_prefix} not exist") def create_tmp_layer(self): tmp_layer_dir = os.path.join(isar_root, 'meta-tmp') @@ -259,82 +299,102 @@ class CIBuilder(Test): os.makedirs(conf_dir, exist_ok=True) layer_conf_file = os.path.join(conf_dir, 'layer.conf') with open(layer_conf_file, 'w') as file: - file.write('\ -BBPATH .= ":${LAYERDIR}"\ -\nBBFILES += "${LAYERDIR}/recipes-*/*/*.bbappend"\ -\nBBFILE_COLLECTIONS += "tmp"\ -\nBBFILE_PATTERN_tmp = "^${LAYERDIR}/"\ -\nBBFILE_PRIORITY_tmp = "5"\ -\nLAYERVERSION_tmp = "1"\ -\nLAYERSERIES_COMPAT_tmp = "v0.6"\ -') - - bblayersconf_file = os.path.join(self.build_dir, 'conf', - 'bblayers.conf') + file.write( + 'BBPATH .= ":${LAYERDIR}"\n' + 'BBFILES += "${LAYERDIR}/recipes-*/*/*.bbappend"\n' + 'BBFILE_COLLECTIONS += "tmp"\n' + 'BBFILE_PATTERN_tmp = "^${LAYERDIR}/"\n' + 'BBFILE_PRIORITY_tmp = "5"\n' + 'LAYERVERSION_tmp = "1"\n' + 'LAYERSERIES_COMPAT_tmp = "v0.6"\n' + ) + + bblayersconf_file = os.path.join( + self.build_dir, 'conf', 'bblayers.conf' + ) bb.utils.edit_bblayers_conf(bblayersconf_file, tmp_layer_dir, None) return tmp_layer_dir def cleanup_tmp_layer(self, tmp_layer_dir): - bblayersconf_file = os.path.join(self.build_dir, 'conf', - 'bblayers.conf') + bblayersconf_file = os.path.join( + self.build_dir, 'conf', 'bblayers.conf' + ) bb.utils.edit_bblayers_conf(bblayersconf_file, None, tmp_layer_dir) bb.utils.prunedir(tmp_layer_dir) def get_ssh_cmd_prefix(self, user, host, port, priv_key): - cmd_prefix = 'ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no '\ - '-p %s -o IdentityFile=%s %s@%s ' \ - % (port, priv_key, user, host) + cmd_prefix = ( + f"ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -p {port} " + f"-o IdentityFile={priv_key} {user}@{host}" + ) return cmd_prefix - def exec_cmd(self, cmd, cmd_prefix): - proc = subprocess.run('exec ' + str(cmd_prefix) + ' "' + str(cmd) + '"', shell=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc = subprocess.run( + f"exec {str(cmd_prefix)} '{str(cmd)}'", + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) return proc.returncode, proc.stdout, proc.stderr - def remote_send_file(self, src, dest, mode): priv_key = self.prepare_priv_key() - cmd_prefix = self.get_ssh_cmd_prefix(self.ssh_user, self.ssh_host, self.ssh_port, priv_key) - - proc = subprocess.run('cat %s | %s install -m %s /dev/stdin %s' % - (src, cmd_prefix, mode, dest), shell=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + cmd_prefix = self.get_ssh_cmd_prefix( + self.ssh_user, self.ssh_host, self.ssh_port, priv_key + ) + + proc = subprocess.run( + f"cat {src} | {cmd_prefix} install -m {mode} /dev/stdin {dest}", + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) return proc.returncode, proc.stdout, proc.stderr def run_script(self, script, cmd_prefix): - script_dir = self.params.get('test_script_dir', - default=os.path.abspath(os.path.dirname(__file__))) + '/scripts/' + file_dirname = os.path.abspath(os.path.dirname(__file__)) + script_dir = self.params.get('test_script_dir', default=file_dirname) + script_dir = script_dir + '/scripts/' script_path = script_dir + script.split()[0] script_args = ' '.join(script.split()[1:]) if not os.path.exists(script_path): - self.log.error('Script not found: ' + script_path) - return (2, '', 'Script not found: ' + script_path) + self.log.error(f"Script not found: {script_path}") + return (2, '', f"Script not found: {script_path}") - rc, stdout, stderr = self.remote_send_file(script_path, "./ci.sh", "755") + rc, stdout, stderr = self.remote_send_file( + script_path, './ci.sh', '755' + ) if rc != 0: - self.log.error('Failed to deploy the script on target') + self.log.error("Failed to deploy the script on target") return (rc, stdout, stderr) time.sleep(1) - proc = subprocess.run('%s ./ci.sh %s' % (cmd_prefix, script_args), shell=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc = subprocess.run( + f"{cmd_prefix} ./ci.sh {script_args}", + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) return (proc.returncode, proc.stdout, proc.stderr) def wait_connection(self, cmd_prefix, timeout): - self.log.info('Waiting for SSH server ready...') + self.log.info("Waiting for SSH server ready...") rc = None - stdout = "" - stderr = "" + stdout = '' + stderr = '' goodcnt = 0 # Use 3 good SSH ping attempts to consider SSH connection is stable @@ -348,33 +408,34 @@ BBPATH .= ":${LAYERDIR}"\ goodcnt = 0 time_left = timeout - time.time() - self.log.info('SSH ping result: %d, left: %.fs' % (rc, time_left)) + self.log.info("SSH ping result: %d, left: %.fs" % (rc, time_left)) return rc, stdout, stderr - def prepare_priv_key(self): - # copy private key to build directory (that is writable) + # Copy private key to build directory (that is writable) priv_key = '%s/ci_priv_key' % self.build_dir if not os.path.exists(priv_key): - shutil.copy(os.path.dirname(__file__) + '/keys/ssh/id_rsa', priv_key) + key = os.path.join(os.path.dirname(__file__), '/keys/ssh/id_rsa') + shutil.copy(key, priv_key) os.chmod(priv_key, 0o400) return priv_key - def remote_run(self, cmd=None, script=None, timeout=0): if cmd: - self.log.info('Remote command is `%s`' % (cmd)) + self.log.info(f"Remote command is `{cmd}`") if script: - self.log.info('Remote script is `%s`' % (script)) + self.log.info(f"Remote script is `{script}`") priv_key = self.prepare_priv_key() - cmd_prefix = self.get_ssh_cmd_prefix(self.ssh_user, self.ssh_host, self.ssh_port, priv_key) + cmd_prefix = self.get_ssh_cmd_prefix( + self.ssh_user, self.ssh_host, self.ssh_port, priv_key + ) rc = None - stdout = "" - stderr = "" + stdout = '' + stderr = '' if timeout != 0: rc, stdout, stderr = self.wait_connection(cmd_prefix, timeout) @@ -382,20 +443,20 @@ BBPATH .= ":${LAYERDIR}"\ if rc == 0 or timeout == 0: if cmd is not None: rc, stdout, stderr = self.exec_cmd(cmd, cmd_prefix) - self.log.info('`' + cmd + '` returned ' + str(rc)) + self.log.info(f"`{cmd}` returned {str(rc)}") elif script is not None: rc, stdout, stderr = self.run_script(script, cmd_prefix) - self.log.info('`' + script + '` returned ' + str(rc)) + self.log.info(f"`{script}` returned {str(rc)}") return rc, stdout, stderr - - def ssh_start(self, user='ci', host='localhost', port=22, - cmd=None, script=None): - self.log.info('===================================================') - self.log.info('Running Isar SSH test for `%s@%s:%s`' % (user, host, port)) - self.log.info('Isar build folder is: ' + self.build_dir) - self.log.info('===================================================') + def ssh_start( + self, user='ci', host='localhost', port=22, cmd=None, script=None + ): + self.log.info("===================================================") + self.log.info(f"Running Isar SSH test for `{user}@{host}:{port}`") + self.log.info(f"Isar build folder is: {self.build_dir}") + self.log.info("===================================================") self.check_init() @@ -404,52 +465,63 @@ BBPATH .= ":${LAYERDIR}"\ self.ssh_port = port priv_key = self.prepare_priv_key() - cmd_prefix = self.get_ssh_cmd_prefix(self.ssh_user, self.ssh_host, self.ssh_port, priv_key) - self.log.info('Connect command:\n' + cmd_prefix) + cmd_prefix = self.get_ssh_cmd_prefix( + self.ssh_user, self.ssh_host, self.ssh_port, priv_key + ) + self.log.info(f"Connect command:\n{cmd_prefix}") if cmd is not None or script is not None: rc, stdout, stderr = self.remote_run(cmd, script) if rc != 0: - self.fail('Failed with rc=%s' % rc) + self.fail(f"Failed with rc={rc}") return stdout, stderr - self.fail('No command to run specified') + self.fail("No command to run specified") - - def vm_turn_on(self, arch='amd64', distro='buster', image='isar-image-base', - enforce_pcbios=False): + def vm_turn_on( + self, + arch='amd64', + distro='buster', + image='isar-image-base', + enforce_pcbios=False, + ): logdir = '%s/vm_start' % self.build_dir if not os.path.exists(logdir): os.mkdir(logdir) - prefix = '%s-vm_start_%s_%s_' % (time.strftime('%Y%m%d-%H%M%S'), - distro, arch) - fd, boot_log = tempfile.mkstemp(suffix='_log.txt', prefix=prefix, - dir=logdir, text=True) + prefix = f"{time.strftime('%Y%m%d-%H%M%S')}-vm_start_{distro}_{arch}_" + fd, boot_log = tempfile.mkstemp( + suffix='_log.txt', prefix=prefix, dir=logdir, text=True + ) os.chmod(boot_log, 0o644) latest_link = '%s/vm_start_%s_%s_latest.txt' % (logdir, distro, arch) if os.path.exists(latest_link): os.unlink(latest_link) os.symlink(os.path.basename(boot_log), latest_link) - cmdline = start_vm.format_qemu_cmdline(arch, self.build_dir, distro, image, - boot_log, None, enforce_pcbios) + cmdline = start_vm.format_qemu_cmdline( + arch, self.build_dir, distro, image, boot_log, None, enforce_pcbios + ) cmdline.insert(1, '-nographic') need_sb_cleanup = start_vm.sb_copy_vars(cmdline) - self.log.info('QEMU boot line:\n' + ' '.join(cmdline)) - self.log.info('QEMU boot log:\n' + boot_log) - - p1 = subprocess.Popen('exec ' + ' '.join(cmdline), shell=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) + self.log.info(f"QEMU boot line:\n{' '.join(cmdline)}") + self.log.info(f"QEMU boot log:\ni{boot_log}") + + p1 = subprocess.Popen( + f"exec {' '.join(cmdline)}", + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + ) self.log.info("Started VM with pid %s" % (p1.pid)) return p1, cmdline, boot_log, need_sb_cleanup - def vm_wait_boot(self, p1, timeout): login_prompt = b' login:' @@ -471,7 +543,7 @@ BBPATH .= ":${LAYERDIR}"\ shift = max(0, len(data) + len(databuf) - databuf_size) databuf = databuf[shift:] + bytearray(data) if login_prompt in databuf: - self.log.info('Got login prompt') + self.log.info("Got login prompt") return 0 if fd == p1.stderr.fileno(): app_log.error(p1.stderr.readline().rstrip()) @@ -479,35 +551,31 @@ BBPATH .= ":${LAYERDIR}"\ self.log.error("Didn't get login prompt") return 1 - def vm_parse_output(self, boot_log, multiconfig, skip_modulecheck): # the printk of recipes-kernel/example-module module_output = b'Just an example' resize_output = None - image_fstypes, \ - wks_file, \ - bbdistro = CIUtils.getVars('IMAGE_FSTYPES', - 'WKS_FILE', - 'DISTRO', - target=multiconfig) + image_fstypes, wks_file, bbdistro = CIUtils.getVars( + 'IMAGE_FSTYPES', 'WKS_FILE', 'DISTRO', target=multiconfig + ) # only the first type will be tested in start_vm if image_fstypes.split()[0] == 'wic': if wks_file: # ubuntu is less verbose so we do not see the message # /etc/sysctl.d/10-console-messages.conf - if bbdistro and "ubuntu" not in bbdistro: - if "sdimage-efi-sd" in wks_file: + if bbdistro and 'ubuntu' not in bbdistro: + if 'sdimage-efi-sd' in wks_file: # output we see when expand-on-first-boot runs on ext4 resize_output = b'resized filesystem to' - if "sdimage-efi-btrfs" in wks_file: + if 'sdimage-efi-btrfs' in wks_file: resize_output = b': resize device ' rc = 0 if os.path.exists(boot_log) and os.path.getsize(boot_log) > 0: - with open(boot_log, "rb") as f1: + with open(boot_log, 'rb') as f1: data = f1.read() - if (module_output in data or skip_modulecheck): - if resize_output and not resize_output in data: + if module_output in data or skip_modulecheck: + if resize_output and resize_output not in data: rc = 1 self.log.error("No resize output while expected") else: @@ -515,13 +583,11 @@ BBPATH .= ":${LAYERDIR}"\ self.log.error("No example module output while expected") return rc - def vm_dump_dict(self, vm): - f = open(self.vm_dict_file, "wb") + f = open(self.vm_dict_file, 'wb') pickle.dump(self.vm_dict, f) f.close() - def vm_turn_off(self, vm): pid = self.vm_dict[vm][0] os.kill(pid, signal.SIGKILL) @@ -529,24 +595,30 @@ BBPATH .= ":${LAYERDIR}"\ if self.vm_dict[vm][3]: start_vm.sb_cleanup() - del(self.vm_dict[vm]) + del self.vm_dict[vm] self.vm_dump_dict(vm) self.log.info("Stopped VM with pid %s" % (pid)) - - def vm_start(self, arch='amd64', distro='buster', - enforce_pcbios=False, skip_modulecheck=False, - image='isar-image-base', cmd=None, script=None, - keep=False): + def vm_start( + self, + arch='amd64', + distro='buster', + enforce_pcbios=False, + skip_modulecheck=False, + image='isar-image-base', + cmd=None, + script=None, + keep=False, + ): time_to_wait = self.params.get('time_to_wait', default=DEF_VM_TO_SEC) - self.log.info('===================================================') - self.log.info('Running Isar VM boot test for (' + distro + '-' + arch + ')') - self.log.info('Remote command is ' + str(cmd)) - self.log.info('Remote script is ' + str(script)) - self.log.info('Isar build folder is: ' + self.build_dir) - self.log.info('===================================================') + self.log.info("===================================================") + self.log.info(f"Running Isar VM boot test for ({distro}-{arch})") + self.log.info(f"Remote command is {str(cmd)}") + self.log.info(f"Remote script is {str(script)}") + self.log.info(f"Isar build folder is: {self.build_dir}") + self.log.info("===================================================") self.check_init() @@ -556,41 +628,50 @@ BBPATH .= ":${LAYERDIR}"\ p1 = None pid = None - cmdline = "" - boot_log = "" + cmdline = '' + boot_log = '' run_qemu = True - stdout = "" - stderr = "" + stdout = '' + stderr = '' if vm in self.vm_dict: pid, cmdline, boot_log, need_sb_cleanup = self.vm_dict[vm] # Check that corresponding process exists - proc = subprocess.run("ps -o cmd= %d" % (pid), shell=True, text=True, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + proc = subprocess.run( + f"ps -o cmd= {pid}", + shell=True, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) if cmdline[0] in proc.stdout: - self.log.info("Found '%s' process with pid '%d', use it" % (cmdline[0], pid)) + self.log.info( + f"Found '{cmdline[0]}' process with pid '{pid}', use it" + ) run_qemu = False if run_qemu: - self.log.info("No qemu-system process for `%s` found, run new VM" % (vm)) + self.log.info( + f"No qemu-system process for `{vm}` found, run new VM" + ) - p1, cmdline, boot_log, \ - need_sb_cleanup = self.vm_turn_on(arch, distro, image, - enforce_pcbios) + p1, cmdline, boot_log, need_sb_cleanup = self.vm_turn_on( + arch, distro, image, enforce_pcbios + ) self.vm_dict[vm] = p1.pid, cmdline, boot_log, need_sb_cleanup self.vm_dump_dict(vm) rc = self.vm_wait_boot(p1, timeout) if rc != 0: self.vm_turn_off(vm) - self.fail('Failed to boot qemu machine') + self.fail("Failed to boot qemu machine") if cmd is not None or script is not None: - self.ssh_user='ci' - self.ssh_host='localhost' + self.ssh_user = 'ci' + self.ssh_host = 'localhost' self.ssh_port = 22 for arg in cmdline: match = re.match(r".*hostfwd=tcp::(\d*).*", arg) @@ -599,21 +680,23 @@ BBPATH .= ":${LAYERDIR}"\ break priv_key = self.prepare_priv_key() - cmd_prefix = self.get_ssh_cmd_prefix(self.ssh_user, self.ssh_host, self.ssh_port, priv_key) - self.log.info('Connect command:\n' + cmd_prefix) + cmd_prefix = self.get_ssh_cmd_prefix( + self.ssh_user, self.ssh_host, self.ssh_port, priv_key + ) + self.log.info(f"Connect command:\n{cmd_prefix}") rc, stdout, stderr = self.remote_run(cmd, script, timeout) if rc != 0: if not keep: self.vm_turn_off(vm) - self.fail('Failed to run test over ssh') + self.fail("Failed to run test over ssh") else: multiconfig = 'mc:qemu' + arch + '-' + distro + ':' + image rc = self.vm_parse_output(boot_log, multiconfig, skip_modulecheck) if rc != 0: if not keep: self.vm_turn_off(vm) - self.fail('Failed to parse output') + self.fail("Failed to parse output") if not keep: self.vm_turn_off(vm) diff --git a/testsuite/citest.py b/testsuite/citest.py index 8dd907d0..4e1634b7 100755 --- a/testsuite/citest.py +++ b/testsuite/citest.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 -import os - from avocado import skipUnless +from avocado.core import exceptions from avocado.utils import path from cibase import CIBaseTest from utils import CIUtils @@ -26,22 +25,23 @@ class DevTest(CIBaseTest): :avocado: tags=dev,fast,full """ + def test_dev(self): targets = [ 'mc:qemuamd64-bullseye:isar-image-ci', 'mc:qemuarm-bullseye:isar-image-base', 'mc:qemuarm-bullseye:isar-image-base:do_populate_sdk', 'mc:qemuarm64-bullseye:isar-image-base', - ] + ] self.init() - self.perform_build_test(targets, image_install="example-raw") + self.perform_build_test(targets, image_install='example-raw') def test_dev_apps(self): targets = [ 'mc:qemuamd64-bullseye:isar-image-ci', 'mc:qemuarm64-bullseye:isar-image-base', - ] + ] self.init() self.perform_build_test(targets) @@ -73,6 +73,7 @@ class DevTest(CIBaseTest): self.init() self.vm_start('arm', 'bullseye', skip_modulecheck=True) + class ReproTest(CIBaseTest): """ @@ -80,12 +81,13 @@ class ReproTest(CIBaseTest): :avocado: tags=repro,full """ + def test_repro_signed(self): targets = [ 'mc:rpi-arm-v7-bullseye:isar-image-base', 'mc:rpi-arm64-v8-bullseye:isar-image-base', 'mc:qemuarm64-bullseye:isar-image-base', - ] + ] self.init() try: @@ -97,7 +99,7 @@ class ReproTest(CIBaseTest): targets = [ 'mc:qemuamd64-bullseye:isar-image-base', 'mc:qemuarm-bullseye:isar-image-base', - ] + ] self.init() try: @@ -105,6 +107,7 @@ class ReproTest(CIBaseTest): finally: self.move_in_build_dir('tmp', 'tmp_repro_unsigned') + class CcacheTest(CIBaseTest): """ @@ -112,11 +115,13 @@ class CcacheTest(CIBaseTest): :avocado: tags=ccache,full """ + def test_ccache_rebuild(self): targets = ['mc:qemuamd64-bullseye:hello-isar'] self.init() self.perform_ccache_test(targets) + class CrossTest(CIBaseTest): """ @@ -124,6 +129,7 @@ class CrossTest(CIBaseTest): :avocado: tags=cross,fast,full """ + def test_cross(self): targets = [ 'mc:qemuarm-buster:isar-image-ci', @@ -135,7 +141,7 @@ class CrossTest(CIBaseTest): 'mc:qemuarm64-focal:isar-image-base', 'mc:nanopi-neo-efi-bookworm:isar-image-base', 'mc:phyboard-mira-bookworm:isar-image-base', - ] + ] self.init() self.perform_build_test(targets, debsrc_cache=True) @@ -143,14 +149,15 @@ class CrossTest(CIBaseTest): def test_cross_rpi(self): targets = [ 'mc:rpi-arm-v7-bullseye:isar-image-base', - ] + ] self.init() try: self.perform_build_test(targets, debsrc_cache=True) - except: + except exceptions.TestFail: self.cancel('KFAIL') + class WicTest(CIBaseTest): """ @@ -158,21 +165,31 @@ class WicTest(CIBaseTest): :avocado: tags=wic,full """ + def test_wic_nodeploy_partitions(self): targets = ['mc:qemuarm64-bookworm:isar-image-ci'] self.init() self.move_in_build_dir('tmp', 'tmp_before_wic') - self.perform_wic_partition_test(targets, - wic_deploy_parts=False, debsrc_cache=True, compat_arch=False) + self.perform_wic_partition_test( + targets, + wic_deploy_parts=False, + debsrc_cache=True, + compat_arch=False, + ) def test_wic_deploy_partitions(self): targets = ['mc:qemuarm64-bookworm:isar-image-ci'] self.init() # reuse artifacts - self.perform_wic_partition_test(targets, - wic_deploy_parts=True, debsrc_cache=True, compat_arch=False) + self.perform_wic_partition_test( + targets, + wic_deploy_parts=True, + debsrc_cache=True, + compat_arch=False, + ) + class NoCrossTest(CIBaseTest): @@ -181,6 +198,7 @@ class NoCrossTest(CIBaseTest): :avocado: tags=nocross,full """ + def test_nocross(self): targets = [ 'mc:qemuarm-buster:isar-image-ci', @@ -209,7 +227,7 @@ class NoCrossTest(CIBaseTest): 'mc:hikey-bookworm:isar-image-base', 'mc:de0-nano-soc-bookworm:isar-image-base', 'mc:beagleplay-bookworm:isar-image-base', - ] + ] self.init() # Cleanup after cross build @@ -226,12 +244,12 @@ class NoCrossTest(CIBaseTest): 'mc:rpi-arm-v7-bookworm:isar-image-base', 'mc:rpi-arm-v7l-bookworm:isar-image-base', 'mc:rpi-arm64-v8-bookworm:isar-image-base', - ] + ] self.init() try: self.perform_build_test(targets, cross=False, debsrc_cache=True) - except: + except exceptions.TestFail: self.cancel('KFAIL') def test_nocross_trixie(self): @@ -239,12 +257,12 @@ class NoCrossTest(CIBaseTest): 'mc:qemuamd64-trixie:isar-image-base', 'mc:qemuarm64-trixie:isar-image-base', 'mc:qemuarm-trixie:isar-image-base', - ] + ] self.init() try: self.perform_build_test(targets, cross=False) - except: + except exceptions.TestFail: self.cancel('KFAIL') def test_nocross_sid(self): @@ -252,14 +270,15 @@ class NoCrossTest(CIBaseTest): 'mc:qemuriscv64-sid:isar-image-base', 'mc:sifive-fu540-sid:isar-image-base', 'mc:starfive-visionfive2-sid:isar-image-base', - ] + ] self.init() try: self.perform_build_test(targets, cross=False) - except: + except exceptions.TestFail: self.cancel('KFAIL') + class ContainerImageTest(CIBaseTest): """ @@ -267,17 +286,19 @@ class ContainerImageTest(CIBaseTest): :avocado: tags=containerbuild,fast,full,container """ + @skipUnless(UMOCI_AVAILABLE and SKOPEO_AVAILABLE, 'umoci/skopeo not found') def test_container_image(self): targets = [ 'mc:container-amd64-buster:isar-image-base', 'mc:container-amd64-bullseye:isar-image-base', 'mc:container-amd64-bookworm:isar-image-base', - ] + ] self.init() self.perform_build_test(targets, container=True) + class ContainerSdkTest(CIBaseTest): """ @@ -285,35 +306,44 @@ class ContainerSdkTest(CIBaseTest): :avocado: tags=containersdk,fast,full,container """ + @skipUnless(UMOCI_AVAILABLE and SKOPEO_AVAILABLE, 'umoci/skopeo not found') def test_container_sdk(self): targets = ['mc:container-amd64-bullseye:isar-image-base'] self.init() - self.perform_build_test(targets, bitbake_cmd='do_populate_sdk', container=True) + self.perform_build_test( + targets, bitbake_cmd='do_populate_sdk', container=True + ) + class SignatureTest(CIBaseTest): + """ Test for signature cachability issues which prevent shared state reuse. - SstateTest also checks for these, but this test is faster and will check more cases. + SstateTest also checks for these, but this test is faster and will check + more cases. :avocado: tags=signatures,sstate """ + def test_signature_lint(self): - verbose = bool(int(self.params.get("verbose", default=0))) + verbose = bool(int(self.params.get('verbose', default=0))) targets = [ 'mc:qemuamd64-bullseye:isar-image-ci', 'mc:qemuarm-bullseye:isar-image-base', 'mc:qemuarm-bullseye:isar-image-base:do_populate_sdk', 'mc:qemuarm64-bullseye:isar-image-base', - 'mc:qemuamd64-focal:isar-image-base' - ] + 'mc:qemuamd64-focal:isar-image-base', + ] self.init() self.perform_signature_lint(targets, verbose=verbose) + class SstateTest(CIBaseTest): + """ Test builds with artifacts taken from sstate cache @@ -332,6 +362,7 @@ class SstateTest(CIBaseTest): self.init('build-sstate') self.perform_sstate_test(image_target, package_target) + class SingleTest(CIBaseTest): """ @@ -339,6 +370,7 @@ class SingleTest(CIBaseTest): :avocado: tags=single """ + def test_single_build(self): self.init() machine = self.params.get('machine', default='qemuamd64') @@ -354,6 +386,7 @@ class SingleTest(CIBaseTest): self.vm_start(machine.removeprefix('qemu'), distro) + class SourceTest(CIBaseTest): """ @@ -361,15 +394,17 @@ class SourceTest(CIBaseTest): :avocado: tags=source """ + def test_source(self): targets = [ 'mc:qemuamd64-bookworm:libhello', 'mc:qemuarm64-bookworm:libhello', - ] + ] self.init() self.perform_source_test(targets) + class VmBootTestFast(CIBaseTest): """ @@ -380,47 +415,72 @@ class VmBootTestFast(CIBaseTest): def test_arm_bullseye(self): self.init() - self.vm_start('arm','bullseye', image='isar-image-ci', keep=True) + self.vm_start('arm', 'bullseye', image='isar-image-ci', keep=True) def test_arm_bullseye_example_module(self): self.init() - self.vm_start('arm','bullseye', image='isar-image-ci', - cmd='lsmod | grep example_module', keep=True) + self.vm_start( + 'arm', + 'bullseye', + image='isar-image-ci', + cmd='lsmod | grep example_module', + keep=True, + ) def test_arm_bullseye_getty_target(self): self.init() - self.vm_start('arm','bullseye', image='isar-image-ci', - script='test_systemd_unit.sh getty.target 10') - + self.vm_start( + 'arm', + 'bullseye', + image='isar-image-ci', + script='test_systemd_unit.sh getty.target 10', + ) def test_arm_buster(self): self.init() - self.vm_start('arm','buster', image='isar-image-ci', keep=True) + self.vm_start('arm', 'buster', image='isar-image-ci', keep=True) def test_arm_buster_getty_target(self): self.init() - self.vm_start('arm','buster', image='isar-image-ci', - cmd='systemctl is-active getty.target', keep=True) + self.vm_start( + 'arm', + 'buster', + image='isar-image-ci', + cmd='systemctl is-active getty.target', + keep=True, + ) def test_arm_buster_example_module(self): self.init() - self.vm_start('arm','buster', image='isar-image-ci', - script='test_kernel_module.sh example_module') - + self.vm_start( + 'arm', + 'buster', + image='isar-image-ci', + script='test_kernel_module.sh example_module', + ) def test_arm_bookworm(self): self.init() - self.vm_start('arm','bookworm', image='isar-image-ci', keep=True) + self.vm_start('arm', 'bookworm', image='isar-image-ci', keep=True) def test_arm_bookworm_example_module(self): self.init() - self.vm_start('arm','bookworm', image='isar-image-ci', - cmd='lsmod | grep example_module', keep=True) + self.vm_start( + 'arm', + 'bookworm', + image='isar-image-ci', + cmd='lsmod | grep example_module', + keep=True, + ) def test_arm_bookworm_getty_target(self): self.init() - self.vm_start('arm','bookworm', image='isar-image-ci', - script='test_systemd_unit.sh getty.target 10') + self.vm_start( + 'arm', + 'bookworm', + image='isar-image-ci', + script='test_systemd_unit.sh getty.target 10', + ) class VmBootTestFull(CIBaseTest): @@ -433,92 +493,119 @@ class VmBootTestFull(CIBaseTest): def test_arm_bullseye(self): self.init() - self.vm_start('arm','bullseye') - + self.vm_start('arm', 'bullseye') def test_arm_buster(self): self.init() - self.vm_start('arm','buster', image='isar-image-ci', keep=True) + self.vm_start('arm', 'buster', image='isar-image-ci', keep=True) def test_arm_buster_example_module(self): self.init() - self.vm_start('arm','buster', image='isar-image-ci', - cmd='lsmod | grep example_module', keep=True) + self.vm_start( + 'arm', + 'buster', + image='isar-image-ci', + cmd='lsmod | grep example_module', + keep=True, + ) def test_arm_buster_getty_target(self): self.init() - self.vm_start('arm','buster', image='isar-image-ci', - script='test_systemd_unit.sh getty.target 10') - + self.vm_start( + 'arm', + 'buster', + image='isar-image-ci', + script='test_systemd_unit.sh getty.target 10', + ) def test_arm64_bullseye(self): self.init() - self.vm_start('arm64','bullseye', image='isar-image-ci', keep=True) + self.vm_start('arm64', 'bullseye', image='isar-image-ci', keep=True) def test_arm64_bullseye_getty_target(self): self.init() - self.vm_start('arm64','bullseye', image='isar-image-ci', - cmd='systemctl is-active getty.target', keep=True) + self.vm_start( + 'arm64', + 'bullseye', + image='isar-image-ci', + cmd='systemctl is-active getty.target', + keep=True, + ) def test_arm64_bullseye_example_module(self): self.init() - self.vm_start('arm64','bullseye', image='isar-image-ci', - script='test_kernel_module.sh example_module') - + self.vm_start( + 'arm64', + 'bullseye', + image='isar-image-ci', + script='test_kernel_module.sh example_module', + ) def test_i386_buster(self): self.init() - self.vm_start('i386','buster') - + self.vm_start('i386', 'buster') def test_amd64_buster(self): self.init() # test efi boot - self.vm_start('amd64','buster', image='isar-image-ci') + self.vm_start('amd64', 'buster', image='isar-image-ci') # test pcbios boot self.vm_start('amd64', 'buster', True, image='isar-image-ci') - def test_amd64_focal(self): self.init() - self.vm_start('amd64','focal', image='isar-image-ci', keep=True) + self.vm_start('amd64', 'focal', image='isar-image-ci', keep=True) def test_amd64_focal_example_module(self): self.init() - self.vm_start('amd64','focal', image='isar-image-ci', - cmd='lsmod | grep example_module', keep=True) + self.vm_start( + 'amd64', + 'focal', + image='isar-image-ci', + cmd='lsmod | grep example_module', + keep=True, + ) def test_amd64_focal_getty_target(self): self.init() - self.vm_start('amd64','focal', image='isar-image-ci', - script='test_systemd_unit.sh getty.target 10') - + self.vm_start( + 'amd64', + 'focal', + image='isar-image-ci', + script='test_systemd_unit.sh getty.target 10', + ) def test_amd64_bookworm(self): self.init() self.vm_start('amd64', 'bookworm', image='isar-image-ci') - def test_arm_bookworm(self): self.init() - self.vm_start('arm','bookworm', image='isar-image-ci') - + self.vm_start('arm', 'bookworm', image='isar-image-ci') def test_i386_bookworm(self): self.init() - self.vm_start('i386','bookworm') - + self.vm_start('i386', 'bookworm') def test_mipsel_bookworm(self): self.init() - self.vm_start('mipsel','bookworm', image='isar-image-ci', keep=True) + self.vm_start('mipsel', 'bookworm', image='isar-image-ci', keep=True) def test_mipsel_bookworm_getty_target(self): self.init() - self.vm_start('mipsel','bookworm', image='isar-image-ci', - cmd='systemctl is-active getty.target', keep=True) + self.vm_start( + 'mipsel', + 'bookworm', + image='isar-image-ci', + cmd='systemctl is-active getty.target', + keep=True, + ) def test_mipsel_bookworm_example_module(self): self.init() - self.vm_start('mipsel','bookworm', image='isar-image-ci', - script='test_kernel_module.sh example_module') + self.vm_start( + 'mipsel', + 'bookworm', + image='isar-image-ci', + script='test_kernel_module.sh example_module', + ) diff --git a/testsuite/repro-build-test.py b/testsuite/repro-build-test.py index 04e4ddc7..d24e8f84 100755 --- a/testsuite/repro-build-test.py +++ b/testsuite/repro-build-test.py @@ -15,32 +15,33 @@ class ReproBuild(CIBuilder): def test_repro_build(self): target = self.params.get( - "build_target", default="mc:qemuamd64-bullseye:isar-image-base" + 'build_target', default='mc:qemuamd64-bullseye:isar-image-base' ) source_date_epoch = self.params.get( - "source_date_epoch", default=self.git_last_commit_timestamp() + 'source_date_epoch', default=self.git_last_commit_timestamp() ) self.init() - self.build_repro_image(target, source_date_epoch, "image1.tar.gz") - self.build_repro_image(target, source_date_epoch, "image2.tar.gz") - self.compare_repro_image("image1.tar.gz", "image2.tar.gz") + self.build_repro_image(target, source_date_epoch, 'image1.tar.gz') + self.build_repro_image(target, source_date_epoch, 'image2.tar.gz') + self.compare_repro_image('image1.tar.gz', 'image2.tar.gz') def git_last_commit_timestamp(self): - return process.run("git log -1 --pretty=%ct").stdout.decode().strip() + return process.run('git log -1 --pretty=%ct').stdout.decode().strip() def get_image_path(self, target_name): - image_dir = "tmp/deploy/images" - machine, image_name = CIUtils.getVars('MACHINE', 'IMAGE_FULLNAME', - target=target_name) + image_dir = 'tmp/deploy/images' + machine, image_name = CIUtils.getVars( + 'MACHINE', 'IMAGE_FULLNAME', target=target_name + ) return f"{image_dir}/{machine}/{image_name}.tar.gz" def build_repro_image( - self, target, source_date_epoch=None, image_name="image.tar.gz" + self, target, source_date_epoch=None, image_name='image.tar.gz' ): - if not source_date_epoch: self.error( - "Reproducible build should configure with source_date_epoch time" + "Reproducible build should configure with " + "source_date_epoch time" ) # clean artifacts before build @@ -48,7 +49,9 @@ class ReproBuild(CIBuilder): # Build self.log.info("Started Build " + image_name) - self.configure(source_date_epoch=source_date_epoch, use_apt_snapshot=True) + self.configure( + source_date_epoch=source_date_epoch, use_apt_snapshot=True + ) self.bitbake(target) # copy the artifacts image name with given name @@ -57,18 +60,16 @@ class ReproBuild(CIBuilder): self.move_in_build_dir(image_path, image_name) def clean(self): - self.delete_from_build_dir("tmp") - self.delete_from_build_dir("sstate-cache") + self.delete_from_build_dir('tmp') + self.delete_from_build_dir('sstate-cache') def compare_repro_image(self, image1, image2): self.log.info( "Compare artifacts image1: " + image1 + ", image2: " + image2 ) result = process.run( - "diffoscope " - "--text " + self.build_dir + "/diffoscope-output.txt" - " " + self.build_dir + "/" + image1 + - " " + self.build_dir + "/" + image2, + f"diffoscope --text {self.build_dir}/diffoscope-output.txt" + f" {self.build_dir}/{image1} {self.build_dir}/{image2}", ignore_status=True, ) if result.exit_status > 0: diff --git a/testsuite/start_vm.py b/testsuite/start_vm.py index d6e04049..2c986344 100755 --- a/testsuite/start_vm.py +++ b/testsuite/start_vm.py @@ -9,43 +9,48 @@ import socket import subprocess import sys import shutil -import time from utils import CIUtils OVMF_VARS_PATH = '/usr/share/OVMF/OVMF_VARS_4M.ms.fd' -def format_qemu_cmdline(arch, build, distro, image, out, pid, enforce_pcbios=False): - multiconfig = f'mc:qemu{arch}-{distro}:{image}' - - image_fstypes, \ - deploy_dir_image, \ - kernel_image, \ - initrd_image, \ - serial, \ - root_dev, \ - qemu_arch, \ - qemu_machine, \ - qemu_cpu, \ - qemu_disk_args = CIUtils.getVars('IMAGE_FSTYPES', - 'DEPLOY_DIR_IMAGE', - 'KERNEL_IMAGE', - 'INITRD_DEPLOY_FILE', - 'MACHINE_SERIAL', - 'QEMU_ROOTFS_DEV', - 'QEMU_ARCH', - 'QEMU_MACHINE', - 'QEMU_CPU', - 'QEMU_DISK_ARGS', - target=multiconfig) + +def format_qemu_cmdline( + arch, build, distro, image, out, pid, enforce_pcbios=False +): + multiconfig = f"mc:qemu{arch}-{distro}:{image}" + + ( + image_fstypes, + deploy_dir_image, + kernel_image, + initrd_image, + serial, + root_dev, + qemu_arch, + qemu_machine, + qemu_cpu, + qemu_disk_args, + ) = CIUtils.getVars( + 'IMAGE_FSTYPES', + 'DEPLOY_DIR_IMAGE', + 'KERNEL_IMAGE', + 'INITRD_DEPLOY_FILE', + 'MACHINE_SERIAL', + 'QEMU_ROOTFS_DEV', + 'QEMU_ARCH', + 'QEMU_MACHINE', + 'QEMU_CPU', + 'QEMU_DISK_ARGS', + target=multiconfig, + ) extra_args = '' - cpu = [''] image_type = image_fstypes.split()[0] base = 'ubuntu' if distro in ['jammy', 'focal'] else 'debian' - rootfs_image = image + '-' + base + '-' + distro + '-qemu' + arch + '.' + image_type + rootfs_image = f"{image}-{base}-{distro}-qemu{arch}.{image_type}" if image_type == 'ext4': kernel_image = deploy_dir_image + '/' + kernel_image @@ -55,33 +60,37 @@ def format_qemu_cmdline(arch, build, distro, image, out, pid, enforce_pcbios=Fal else: initrd_image = deploy_dir_image + '/' + initrd_image - kargs = ['-append', '"console=' + serial + ' root=/dev/' + root_dev + ' rw"'] + kargs = ['-append', f'"console={serial} root=/dev/{root_dev} rw"'] extra_args = ['-kernel', kernel_image, '-initrd', initrd_image] extra_args.extend(kargs) elif image_type == 'wic': extra_args = ['-snapshot'] else: - raise ValueError('Invalid image type: ' + str(image_type)) + raise ValueError(f"Invalid image type: {str(image_type)}") if out: - extra_args.extend(['-chardev','stdio,id=ch0,logfile=' + out]) - extra_args.extend(['-serial','chardev:ch0']) - extra_args.extend(['-monitor','none']) + extra_args.extend(['-chardev', 'stdio,id=ch0,logfile=' + out]) + extra_args.extend(['-serial', 'chardev:ch0']) + extra_args.extend(['-monitor', 'none']) if pid: extra_args.extend(['-pidfile', pid]) - qemu_disk_args = qemu_disk_args.replace('##ROOTFS_IMAGE##', deploy_dir_image + '/' + rootfs_image).split() + rootfs_path = os.path.join(deploy_dir_image, rootfs_image) + qemu_disk_args = qemu_disk_args.replace('##ROOTFS_IMAGE##', rootfs_path) + qemu_disk_args = qemu_disk_args.split() if enforce_pcbios and '-bios' in qemu_disk_args: bios_idx = qemu_disk_args.index('-bios') - del qemu_disk_args[bios_idx : bios_idx+2] + del qemu_disk_args[bios_idx : bios_idx + 2] # Support SSH access from host ssh_sock = socket.socket() ssh_sock.bind(('', 0)) - ssh_port=ssh_sock.getsockname()[1] + ssh_port = ssh_sock.getsockname()[1] extra_args.extend(['-device', 'e1000,netdev=net0']) - extra_args.extend(['-netdev', 'user,id=net0,hostfwd=tcp::' + str(ssh_port) + '-:22']) + extra_args.extend( + ['-netdev', 'user,id=net0,hostfwd=tcp::' + str(ssh_port) + '-:22'] + ) cmd = ['qemu-system-' + qemu_arch, '-m', '1024M'] @@ -105,8 +114,10 @@ def sb_copy_vars(cmdline): if os.path.exists(ovmf_vars_filename): break if not os.path.exists(OVMF_VARS_PATH): - print(f'{OVMF_VARS_PATH} required but not found!', - file=sys.stderr) + print( + f"{OVMF_VARS_PATH} required but not found!", + file=sys.stderr, + ) break shutil.copy(OVMF_VARS_PATH, ovmf_vars_filename) return True @@ -119,7 +130,9 @@ def sb_cleanup(): def start_qemu(arch, build, distro, image, out, pid, enforce_pcbios): - cmdline = format_qemu_cmdline(arch, build, distro, image, out, pid, enforce_pcbios) + cmdline = format_qemu_cmdline( + arch, build, distro, image, out, pid, enforce_pcbios + ) cmdline.insert(1, '-nographic') need_cleanup = sb_copy_vars(cmdline) @@ -136,17 +149,60 @@ def start_qemu(arch, build, distro, image, out, pid, enforce_pcbios): def parse_args(): parser = argparse.ArgumentParser() arch_names = ['arm', 'arm64', 'amd64', 'amd64-sb', 'i386', 'mipsel'] - parser.add_argument('-a', '--arch', choices=arch_names, - help='set isar machine architecture.', default='arm') - parser.add_argument('-b', '--build', help='set path to build directory.', default=os.getcwd()) - parser.add_argument('-d', '--distro', choices=['buster', 'bullseye', 'bookworm', 'trixie', 'focal', 'jammy'], help='set isar Debian distribution.', default='bookworm') - parser.add_argument('-i', '--image', help='set image name.', default='isar-image-base') - parser.add_argument('-o', '--out', help='Route QEMU console output to specified file.') - parser.add_argument('-p', '--pid', help='Store QEMU pid to specified file.') - parser.add_argument('--pcbios', action="store_true", help='remove any bios options to enforce use of pc bios') + distro_names = [ + 'buster', + 'bullseye', + 'bookworm', + 'trixie', + 'focal', + 'jammy', + ] + parser.add_argument( + '-a', + '--arch', + choices=arch_names, + help='set isar machine architecture.', + default='arm', + ) + parser.add_argument( + '-b', + '--build', + help='set path to build directory.', + default=os.getcwd(), + ) + parser.add_argument( + '-d', + '--distro', + choices=distro_names, + help='set isar Debian distribution.', + default='bookworm', + ) + parser.add_argument( + '-i', '--image', help='set image name.', default='isar-image-base' + ) + parser.add_argument( + '-o', '--out', help='Route QEMU console output to specified file.' + ) + parser.add_argument( + '-p', '--pid', help='Store QEMU pid to specified file.' + ) + parser.add_argument( + '--pcbios', + action='store_true', + help='remove any ' 'bios options to enforce use of pc bios', + ) return parser.parse_args() -if __name__ == "__main__": + +if __name__ == '__main__': args = parse_args() - start_qemu(args.arch, args.build, args.distro, args.image, args.out, args.pid, args.pcbios) + start_qemu( + args.arch, + args.build, + args.distro, + args.image, + args.out, + args.pid, + args.pcbios, + ) diff --git a/testsuite/unittests/bitbake.py b/testsuite/unittests/bitbake.py index 1e2f685a..66cd0b2c 100644 --- a/testsuite/unittests/bitbake.py +++ b/testsuite/unittests/bitbake.py @@ -3,35 +3,35 @@ # # SPDX-License-Identifier: MIT +import os import sys -import pathlib from typing import Callable -location = pathlib.Path(__file__).parent.resolve() -sys.path.insert(0, "{}/../../bitbake/lib".format(location)) +location = os.path.dirname(__file__) +sys.path.append(os.path.join(location, "../../bitbake/lib")) from bb.parse import handle from bb.data import init -# Modules added for reimport from testfiles -from bb.data_smart import DataSmart - def load_function(file_name: str, function_name: str) -> Callable: """Load a python function defined in a bitbake file. Args: - file_name (str): The path to the file e.g. `meta/classes/my_special.bbclass`. - function_name (str): The name of the python function without braces e.g. `my_special_function` + file_name (str): The path to the file + e.g. `meta/classes/my_special.bbclass`. + function_name (str): The name of the python function without braces + e.g. `my_special_function` Returns: Callable: The loaded function. """ d = init() - parse = handle("{}/../../{}".format(location, file_name), d) + parse = handle(f"{location}/../../{file_name}", d) if function_name not in parse: - raise KeyError("Function {} does not exist in {}".format( - function_name, file_name)) + raise KeyError( + f"Function {function_name} does not exist in {file_name}" + ) namespace = {} exec(parse[function_name], namespace) return namespace[function_name] diff --git a/testsuite/unittests/rootfs.py b/testsuite/unittests/rootfs.py index 6c511493..da97d0d3 100644 --- a/testsuite/unittests/rootfs.py +++ b/testsuite/unittests/rootfs.py @@ -12,7 +12,7 @@ temp_dirs = [] class TemporaryRootfs: - """ A temporary rootfs folder that will be removed after the testrun. """ + """A temporary rootfs folder that will be removed after the testrun.""" def __init__(self): self._rootfs_path = tempfile.mkdtemp() @@ -22,7 +22,7 @@ class TemporaryRootfs: return self._rootfs_path def create_file(self, path: str, content: str) -> None: - """ Create a file with the given content. + """Create a file with the given content. Args: path (str): The path to the file e.g. `/etc/hostname`. @@ -31,8 +31,9 @@ class TemporaryRootfs: Returns: None """ - pathlib.Path(self._rootfs_path + - path).parent.mkdir(parents=True, exist_ok=True) + pathlib.Path(self._rootfs_path + path).parent.mkdir( + parents=True, exist_ok=True + ) with open(self._rootfs_path + path, 'w') as file: file.write(content) diff --git a/testsuite/unittests/test_image_account_extension.py b/testsuite/unittests/test_image_account_extension.py index 08021a4a..636c2a8b 100644 --- a/testsuite/unittests/test_image_account_extension.py +++ b/testsuite/unittests/test_image_account_extension.py @@ -3,158 +3,202 @@ # # SPDX-License-Identifier: MIT -from bitbake import load_function, DataSmart +from bitbake import load_function from rootfs import TemporaryRootfs +import os +import sys import unittest from unittest.mock import patch from typing import Tuple +sys.path.append(os.path.join(os.path.dirname(__file__), '../../bitbake/lib')) -file_name = "meta/classes/image-account-extension.bbclass" -image_create_users = load_function(file_name, "image_create_users") -image_create_groups = load_function(file_name, "image_create_groups") +from bb import process +from bb.data_smart import DataSmart +file_name = 'meta/classes/image-account-extension.bbclass' +image_create_users = load_function(file_name, 'image_create_users') +image_create_groups = load_function(file_name, 'image_create_groups') -class TestImageAccountExtensionCommon(unittest.TestCase): +class TestImageAccountExtensionCommon(unittest.TestCase): def setup(self) -> Tuple[DataSmart, TemporaryRootfs]: rootfs = TemporaryRootfs() d = DataSmart() - d.setVar("ROOTFSDIR", rootfs.path()) + d.setVar('ROOTFSDIR', rootfs.path()) return (d, rootfs) -class TestImageAccountExtensionImageCreateUsers(TestImageAccountExtensionCommon): - +class TestImageAccountExtensionImageCreateUsers( + TestImageAccountExtensionCommon +): def setup(self, user_name: str) -> Tuple[DataSmart, TemporaryRootfs]: d, rootfs = super().setup() rootfs.create_file( - "/etc/passwd", "test:x:1000:1000::/home/test:/bin/sh") - d.setVar("USERS", user_name) + '/etc/passwd', 'test:x:1000:1000::/home/test:/bin/sh' + ) + d.setVar('USERS', user_name) return (d, rootfs) def test_new_user(self): - test_user = "new" + test_user = 'new' d, rootfs = self.setup(test_user) - # make the list a bit clumsy to simulate appends and removals to that var - d.setVarFlag('USER_{}'.format(test_user), 'groups', 'dialout render foo ') + # Make the list a bit clumsy to simulate appends and removals to that + # var + d.setVarFlag(f"USER_{test_user}", 'groups', 'dialout render foo ') - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_users(d) run_mock.assert_called_once_with( - ["sudo", "-E", "chroot", rootfs.path(), "/usr/sbin/useradd", - '--groups', 'dialout,render,foo', test_user]) + [ + 'sudo', + '-E', + 'chroot', + rootfs.path(), + '/usr/sbin/useradd', + '--groups', + 'dialout,render,foo', + test_user, + ] + ) def test_existing_user_no_change(self): - test_user = "test" + test_user = 'test' d, _ = self.setup(test_user) - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_users(d) run_mock.assert_not_called() def test_existing_user_home_change(self): - test_user = "test" + test_user = 'test' d, _ = self.setup(test_user) - d.setVarFlag("USER_{}".format(test_user), "home", "/home/new_home") + d.setVarFlag(f"USER_{test_user}", 'home', '/home/new_home') - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_users(d) assert run_mock.call_count == 1 - assert run_mock.call_args[0][0][-5:] == ["/usr/sbin/usermod", - '--home', '/home/new_home', '--move-home', 'test'] + assert run_mock.call_args[0][0][-5:] == [ + '/usr/sbin/usermod', + '--home', + '/home/new_home', + '--move-home', + 'test', + ] def test_deterministic_password(self): - test_user = "new" - cleartext_password = "test" + test_user = 'new' + cleartext_password = 'test' d, _ = self.setup(test_user) - d.setVarFlag("USER_{}".format(test_user), - "flags", "clear-text-password") - d.setVarFlag("USER_{}".format(test_user), - "password", cleartext_password) + d.setVarFlag(f"USER_{test_user}", 'flags', 'clear-text-password') + d.setVarFlag(f"USER_{test_user}", 'password', cleartext_password) - source_date_epoch = "1672427776" - d.setVar("SOURCE_DATE_EPOCH", source_date_epoch) + source_date_epoch = '1672427776' + d.setVar('SOURCE_DATE_EPOCH', source_date_epoch) - # openssl passwd -6 -salt $(echo "1672427776" | sha256sum -z | cut -c 1-15) test - encrypted_password = "$6$eb2e2a12cccc88a$IuhgisFe5AKM5.VREKg8wIAcPSkaJDWBM1cMUsEjNZh2Wa6BT2f5OFhqGTGpL4lFzHGN8oiwvAh0jFO1GhO3S." + # openssl passwd -6 -salt $(echo "1672427776" | sha256sum -z | cut \ + # -c 1-15) test + encrypted_password = ( + '$6$eb2e2a12cccc88a$IuhgisFe5AKM5.VREKg8wIAcPSkaJDWBM1cMUsEjNZh2W' + 'a6BT2f5OFhqGTGpL4lFzHGN8oiwvAh0jFO1GhO3S.' + ) - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_users(d) - assert run_mock.call_count == 2 - assert run_mock.call_args[0][1] == "{}:{}".format( - test_user, encrypted_password).encode() + password_data = f"{test_user}:{encrypted_password}".encode() + assert run_mock.call_count == 2 + assert run_mock.call_args[0][1] == password_data -class TestImageAccountExtensionImageCreateGroups(TestImageAccountExtensionCommon): +class TestImageAccountExtensionImageCreateGroups( + TestImageAccountExtensionCommon +): def setup(self, group_name: str) -> Tuple[DataSmart, TemporaryRootfs]: d, rootfs = super().setup() - rootfs.create_file("/etc/group", "test:x:1000:test") - d.setVar("GROUPS", group_name) + rootfs.create_file('/etc/group', 'test:x:1000:test') + d.setVar('GROUPS', group_name) return (d, rootfs) def test_new_group(self): - test_group = "new" + test_group = 'new' d, rootfs = self.setup(test_group) - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_groups(d) run_mock.assert_called_once_with( - ["sudo", "-E", "chroot", rootfs.path(), "/usr/sbin/groupadd", test_group]) + [ + 'sudo', + '-E', + 'chroot', + rootfs.path(), + '/usr/sbin/groupadd', + test_group, + ] + ) def test_existing_group_no_change(self): - test_group = "test" + test_group = 'test' d, _ = self.setup(test_group) - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_groups(d) run_mock.assert_not_called() def test_existing_group_id_change(self): - test_group = "test" + test_group = 'test' d, rootfs = self.setup(test_group) - d.setVarFlag("GROUP_{}".format(test_group), "gid", "1005") + d.setVarFlag(f"GROUP_{test_group}", 'gid', '1005') - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_groups(d) run_mock.assert_called_once_with( - ["sudo", "-E", "chroot", rootfs.path(), "/usr/sbin/groupmod", "--gid", "1005", test_group]) + [ + 'sudo', + '-E', + 'chroot', + rootfs.path(), + '/usr/sbin/groupmod', + '--gid', + '1005', + test_group, + ] + ) def test_new_group_system_flag(self): - test_group = "new" + test_group = 'new' d, _ = self.setup(test_group) - d.setVarFlag("GROUP_{}".format(test_group), "flags", "system") + d.setVarFlag(f"GROUP_{test_group}", 'flags', 'system') - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_groups(d) assert run_mock.call_count == 1 - assert "--system" in run_mock.call_args[0][0] + assert '--system' in run_mock.call_args[0][0] def test_existing_group_no_system_flag(self): - test_group = "test" + test_group = 'test' d, _ = self.setup(test_group) - d.setVarFlag("GROUP_{}".format(test_group), "flags", "system") - d.setVarFlag("GROUP_{}".format(test_group), "gid", "1005") + d.setVarFlag(f"GROUP_{test_group}", 'flags', 'system') + d.setVarFlag(f"GROUP_{test_group}", 'gid', '1005') - with patch.object(bb.process, "run") as run_mock: + with patch.object(process, 'run') as run_mock: image_create_groups(d) assert run_mock.call_count == 1 - assert "--system" not in run_mock.call_args[0][0] + assert '--system' not in run_mock.call_args[0][0] -if __name__ == "__main__": +if __name__ == '__main__': unittest.main()