From nobody Fri Apr 19 00:43:02 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=patchew-devel-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=patchew-devel-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1556795922; cv=none; d=zoho.com; s=zohoarc; b=PkUuy/de1QvGTqWUqdlPiyU3BFiHoIaJQQroXSa9j5A2nCc3q0Jx5c6IKhIIQ13g3Ks8AXDDJwis2ioMg+ERcT3HVWqUrIvDLxk34GSxnoNl1C/MrQnRd5PceU8e82RkyXySqKPZEj8cH1acBUEMvVAiRklFLXf+suDEy/p57yw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1556795922; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=STXMwutNzCS61M2Xhyd75lrlYGmCg2QkQ/N+c0hhPUo=; b=hvlhoSZilYg0dMA5Yw3KJR7Vhnt37dHzxIMecVK6s1M0tQehBYQr5IW32UIl99ElzpX6imK2IDKktJrNGAcwkoDutGDJ26UjYki5ggWKBsWKIhmG+EHVsNwkF0iEdF4g+a2CCTgtBe9AeV0dcUZ99MwiIHOARSYZbeHhuf5M58g= ARC-Authentication-Results: i=1; mx.zoho.com; dkim=fail; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=patchew-devel-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1556795922459975.6733072376954; Thu, 2 May 2019 04:18:42 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id CF19B330246; Thu, 2 May 2019 11:18:38 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id C3BAE39C1; Thu, 2 May 2019 11:18:38 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id B92083FB11; Thu, 2 May 2019 11:18:38 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x42BIaQV009096 for ; Thu, 2 May 2019 07:18:36 -0400 Received: by smtp.corp.redhat.com (Postfix) id D34E2174B1; Thu, 2 May 2019 11:18:36 +0000 (UTC) Received: from mx1.redhat.com (ext-mx09.extmail.prod.ext.phx2.redhat.com [10.5.110.38]) by smtp.corp.redhat.com (Postfix) with ESMTPS id CD3CB39C1 for ; Thu, 2 May 2019 11:18:34 +0000 (UTC) Received: from mail-oi1-f194.google.com (mail-oi1-f194.google.com [209.85.167.194]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id AE7EE6698E for ; Thu, 2 May 2019 11:18:16 +0000 (UTC) Received: by mail-oi1-f194.google.com with SMTP id d62so1269334oib.13 for ; Thu, 02 May 2019 04:18:16 -0700 (PDT) Received: from localhost.localdomain ([198.59.53.9]) by smtp.gmail.com with ESMTPSA id o1sm19095697otj.11.2019.05.02.04.18.14 for (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 02 May 2019 04:18:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=C3R0kgVOONH4hRdDnCvmjYMrCVz/7HAXyV1TbglwS8s=; b=ZkQ8zcmCfwOANLSmsFvwWylf2cvLwndJlZoN2TJ2W1thUcaUKl0cQ/NkPQJC5hzMPK 6rVokr6/ORADMFCetvCoZTDsjzb5GXI36rLLJbhhj0s8yuVQKBLK95C2FdBP4D+vcMTa YWgZ0aUGA985xVbDM0SBO3WgiIP733OqiR/tM31oTpQkEb39PS6KuYjYSOTxLzNjGS8K dXjmz4KzU9ZWsvt4ADS+hCJl6rMqN85Y5VH/Vp7NpZsNlyaCOyZHQmybb9OTTKvgr/4T ot8s8m4DbgCptWADI4uH2jRz19Y2Z4mARbcw8FpjIRj3HeSCEN3uy8RkM8w1jXcbQVil 4+wQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:subject:date:message-id :in-reply-to:references:mime-version:content-transfer-encoding; bh=C3R0kgVOONH4hRdDnCvmjYMrCVz/7HAXyV1TbglwS8s=; b=gL3bWd5pN6FB79UC+dw3OsgvAJPxjyjBm0FwVvdtzjUXJ4kAcUAWpZpNclbK7For85 Uh7KO1NlUZM6B4u9Y8bORwzMF1gCHgmBbMS8/WbTCpCN6Tqo2sTLiQezgNFHlCfVTw/K BZirSS4qIw8+ELJZbyx9LLVfHIUopXKRrordMrum71hgUGqk72qBGPz9R4mJZanHwaSw r/ulrLwrvkYGnG7JUrCVKtnbTvxjAmlmiJ+NrdgY+bdacY63504o+FZqOnSRbUUlos1n VwOD9DqIlGsJhDt4HnUWPuArG5j8SSRu7G197kQFD9dPC4diFp814wWqErs2j3e/fzQS 5HUQ== X-Gm-Message-State: APjAAAVD7MXjtAIX8FHKXtfRsZOxnvt9HOvSue+tqZREPBZwoHUVLaoR rd6mipYZljCdzHiqaC4CaJG6mQTOKsg= X-Google-Smtp-Source: APXvYqzOCUTfx6CUinQSTNNKqW8XQTicD8wSp/zxJ+36ZrkNvHjReyjtROYbPROxZLZ6YI1mzSoj6Q== X-Received: by 2002:aca:3746:: with SMTP id e67mr1954287oia.37.1556795895708; Thu, 02 May 2019 04:18:15 -0700 (PDT) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 2 May 2019 05:17:59 -0600 Message-Id: <20190502111804.15843-13-pbonzini@redhat.com> In-Reply-To: <20190502111804.15843-1-pbonzini@redhat.com> References: <20190502111804.15843-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Thu, 02 May 2019 11:18:16 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Thu, 02 May 2019 11:18:16 +0000 (UTC) for IP:'209.85.167.194' DOMAIN:'mail-oi1-f194.google.com' HELO:'mail-oi1-f194.google.com' FROM:'paolo.bonzini@gmail.com' RCPT:'' X-RedHat-Spam-Score: 0.146 (DKIM_SIGNED, DKIM_VALID, FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, SPF_PASS, T_FILL_THIS_FORM_SHORT) 209.85.167.194 mail-oi1-f194.google.com 209.85.167.194 mail-oi1-f194.google.com X-RedHat-Possible-Forgery: Paolo Bonzini X-Scanned-By: MIMEDefang 2.78 on 10.5.110.38 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 12/17] models: store plugin configuration in a single, separate JSONField X-BeenThere: patchew-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Patchew development and discussion list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.29]); Thu, 02 May 2019 11:18:38 +0000 (UTC) X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Type: text/plain; charset="utf-8" This is the next step in the splitting the properties table into logically separate components. This time it's the turn of configuration data coming from the plugin schemas, which is extracted into a new JSON field. The configuration editor is simplified a bit because all the properties are sent to the server in a single shot and all configuration is overwritten. As a result, we no longer need the deletion API delete-project-properties-by-prefix. Signed-off-by: Paolo Bonzini --- api/migrations/0046_project_config.py | 22 ++++++++ .../0047_populate_project_config.py | 56 +++++++++++++++++++ api/models.py | 11 ++++ api/views.py | 29 ++++------ mod.py | 34 +---------- mods/git.py | 27 ++++----- mods/testing.py | 7 ++- static/js/config-editor.js | 23 ++------ tests/patchewtest.py | 12 ++-- tests/test_git.py | 11 +++- tests/test_testing.py | 32 +++++++---- 11 files changed, 162 insertions(+), 102 deletions(-) create mode 100644 api/migrations/0046_project_config.py create mode 100644 api/migrations/0047_populate_project_config.py diff --git a/api/migrations/0046_project_config.py b/api/migrations/0046_pr= oject_config.py new file mode 100644 index 0000000..f61c4c8 --- /dev/null +++ b/api/migrations/0046_project_config.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-18 12:56 +from __future__ import unicode_literals + +from django.db import migrations +import jsonfield.encoder +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0045_message_maintainers'), + ] + + operations =3D [ + migrations.AddField( + model_name=3D'project', + name=3D'config', + field=3Djsonfield.fields.JSONField(default=3D{}, dump_kwargs= =3D{'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_k= wargs=3D{}), + ), + ] diff --git a/api/migrations/0047_populate_project_config.py b/api/migration= s/0047_populate_project_config.py new file mode 100644 index 0000000..2a86e1f --- /dev/null +++ b/api/migrations/0047_populate_project_config.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations +from django.db.models import Q + +def properties_to_config(apps, schema_editor): + Project =3D apps.get_model('api', 'Project') + for po in Project.objects.all(): + q =3D Q(name__startswith=3D'testing.tests.') | \ + Q(name__startswith=3D'testing.requirements.') | \ + Q(name__startswith=3D'email.notifications.') | \ + Q(name__in=3D('git.push_to', 'git.url_template', 'git.public_r= epo')) + props =3D po.projectproperty_set.filter(q) + config =3D {} + for p in props: + *path, last =3D p.name.split('.') + parent =3D config + for item in path: + parent =3D parent.setdefault(item, {}) + parent[last] =3D p.value + #print(po, config) + po.config =3D config + po.save() + props.delete() + +def flatten_properties(source, prefix, result=3DNone): + if result is None: + result =3D {} + for k, v in source.items(): + if isinstance(v, dict): + flatten_properties(v, prefix + k + '.', result) + else: + result[prefix + k] =3D v + return result + +def config_to_properties(apps, schema_editor): + Project =3D apps.get_model('api', 'Project') + ProjectProperty =3D apps.get_model('api', 'ProjectProperty') + for po in Project.objects.all(): + props =3D flatten_properties(po.config, '') + for k, v in props.items(): + new_prop =3D ProjectProperty(project=3Dpo, name=3Dk, value=3Dv) + new_prop.save() + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0046_project_config'), + ] + + operations =3D [ + migrations.RunPython(properties_to_config, + reverse_code=3Dconfig_to_properties), + ] diff --git a/api/models.py b/api/models.py index d2dafde..68e90e1 100644 --- a/api/models.py +++ b/api/models.py @@ -171,6 +171,7 @@ class Project(models.Model): "top project which has " "parent_project=3DNULL")) maintainers =3D models.ManyToManyField(User, blank=3DTrue) + config =3D jsonfield.JSONField(default=3D{}) =20 def __str__(self): return self.name @@ -179,6 +180,13 @@ class Project(models.Model): def has_project(self, project): return self.objects.filter(name=3Dproject).exists() =20 + def save(self, *args, **kwargs): + old_project =3D Project.objects.filter(pk=3Dself.pk).first() + old_config =3D old_project.config if old_project else None + super(Project, self).save(*args, **kwargs) + if old_config !=3D self.config: + emit_event("SetProjectConfig", obj=3Dself) + def get_property(self, prop, default=3DNone): a =3D ProjectProperty.objects.filter(project=3Dself, name=3Dprop).= first() if a: @@ -311,6 +319,9 @@ declare_event("SeriesMerged", project=3D"project object= ", declare_event("MessageAdded", message=3D"message object that is added") =20 =20 +declare_event("SetProjectConfig", obj=3D"project whose configuration was u= pdated") + + declare_event("SetProperty", obj=3D"object to set the property", name=3D"name of the property", value=3D"value of the property", diff --git a/api/views.py b/api/views.py index 887f9ed..672cdc8 100644 --- a/api/views.py +++ b/api/views.py @@ -129,28 +129,23 @@ class UpdateProjectHeadView(APILoginRequiredView): return ret =20 =20 -class SetProjectPropertiesView(APILoginRequiredView): - name =3D "set-project-properties" +class SetProjectConfigView(APILoginRequiredView): + name =3D "set-project-config" allowed_groups =3D ["maintainers"] =20 - def handle(self, request, project, properties): + def handle(self, request, project, config): po =3D Project.objects.get(name=3Dproject) if not po.maintained_by(request.user): raise PermissionDenied("Access denied to this project") - for k, v in properties.items(): - po.set_property(k, v) - - -class DeleteProjectPropertiesByPrefixView(APILoginRequiredView): - name =3D "delete-project-properties-by-prefix" - allowed_groups =3D ["maintainers"] - - def handle(self, request, project, prefix): - po =3D Project.objects.get(name=3Dproject) - if not po.maintained_by(request.user): - raise PermissionDenied("Access denied to this project") - for k in [x for x in po.get_properties().keys() if x.startswith(pr= efix)]: - po.set_property(k, None) + new_config =3D {} + for k, v in config.items(): + *path, last =3D k.split('.') + parent =3D new_config + for item in path: + parent =3D parent.setdefault(item, {}) + parent[last] =3D v + po.config =3D new_config + po.save() =20 =20 def prepare_patch(p): diff --git a/mod.py b/mod.py index 1b7910c..f6db0ce 100644 --- a/mod.py +++ b/mod.py @@ -121,40 +121,8 @@ class PatchewModule(object): config =3D self.get_project_config(project) return self._build_one(request, project, scm.name, config, scm) =20 - def _get_map_scm(self, project, prop_name, scm): - prefix =3D prop_name + "." - result =3D {} - for p in project.get_properties().keys(): - if not p.startswith(prefix): - continue - name =3D p[len(prefix):] - name =3D name[:name.rfind(".")] - if name in result: - continue - assert scm.item.name =3D=3D '{name}' - value =3D self._get_one(project, prefix + name, scm.item) - result[name] =3D value - return result - - def _get_array_scm(self, project, prop_name, scm): - prefix =3D prop_name + "." - result =3D {} - for i in scm.members: - assert i.name !=3D '{name}' - result[i.name] =3D self._get_one(project, prefix + i.name, i) - return result - - def _get_one(self, project, prop_name, scm): - if type(scm) =3D=3D MapSchema: - return self._get_map_scm(project, prop_name, scm) - elif type(scm) =3D=3D ArraySchema: - return self._get_array_scm(project, prop_name, scm) - else: - return project.get_property(prop_name) - def get_project_config(self, project): - scm =3D self.project_config_schema - return self._get_one(project, scm.name, scm) + return project.config.get(self.project_config_schema.name, {}) =20 _loaded_modules =3D {} =20 diff --git a/mods/git.py b/mods/git.py index d459a0b..8e868c2 100644 --- a/mods/git.py +++ b/mods/git.py @@ -246,19 +246,20 @@ class ApplierGetView(APILoginRequiredView): def handle(self, request, target_repo=3DNone): q =3D Message.objects.filter(results__name=3D"git", results__statu= s=3D"pending") if target_repo is not None and target_repo !=3D '': - props =3D ProjectProperty.objects.filter(name=3D'git.push_to') - # unfortunately startswith does not work with JSONFields, so w= e have to hack - # around it and look at the raw database representation. This= would fail - # if we used PostgreSQL json fields! - target_repo_escaped =3D json.dumps(target_repo) - if target_repo[-1] !=3D '/': - props =3D props.extra(where=3D["value =3D %s or value like= %s"], - params=3D[target_repo_escaped, - target_repo_escaped[0:-1] + '/= %']) - else: - props =3D props.extra(where=3D["value like %s"], - params=3Dtarget_repo_escaped[0:-1] + '= %') - q =3D q.filter(project__in=3D[prop.project for prop in props.a= ll()]) + # Postgres could use JSON fields instead. Fortunately project= s are + # few so this is cheap + def match_target_repo(config, target_repo): + push_to =3D config.get('git', {}).get('push_to') + if push_to is None: + return False + if target_repo[-1] !=3D '/': + return push_to =3D=3D target_repo or push_to.startswit= h(target_repo + '/') + else: + return push_to.startswith(target_repo) + + projects =3D Project.objects.values_list('id', 'config').all() + projects =3D [pid for pid, config in projects if match_target_= repo(config, target_repo)] + q =3D q.filter(project__pk__in=3Dprojects) m =3D q.first() if not m: return None diff --git a/mods/testing.py b/mods/testing.py index 0125288..2c45001 100644 --- a/mods/testing.py +++ b/mods/testing.py @@ -117,15 +117,16 @@ class TestingModule(PatchewModule): html_log_url=3D"URL to test log (HTML)", is_timeout=3D"whether the test has timeout") register_handler("SetProperty", self.on_set_property) + register_handler("SetProjectConfig", self.on_set_config) register_handler("ResultUpdate", self.on_result_update) =20 def on_set_property(self, evt, obj, name, value, old_value): if isinstance(obj, Project) and name =3D=3D "git.head" \ and old_value !=3D value: self.clear_and_start_testing(obj) - elif isinstance(obj, Project) and name.startswith("testing.tests."= ) \ - and old_value !=3D value: - self.project_recalc_pending_tests(obj) + + def on_set_config(self, evt, obj): + self.project_recalc_pending_tests(obj) =20 def get_msg_base_tags(self, msg): return [t for t in msg.tags if t.lower().startswith("based-on:")] diff --git a/static/js/config-editor.js b/static/js/config-editor.js index 562077d..aae5302 100644 --- a/static/js/config-editor.js +++ b/static/js/config-editor.js @@ -70,9 +70,9 @@ function properties_save(btn) { $(btn).addClass("disabled"); $(btn).text("Saving..."); $(btn).parent().find(".save-message").remove(); - patchew_api_do("set-project-properties", + patchew_api_do("set-project-config", { project: current_project(), - properties: props }) + config: props }) .done(function (data) { save_done(btn, true); }) @@ -118,26 +118,11 @@ function map_add_item(btn) { function map_delete_item(btn) { item =3D $(btn).parent().parent().parent(); name =3D item.find(".item-name").text(); - prefix =3D item.data('property-prefix') + "."; if (!window.confirm("Really delete '" + name +"'?")) { return; } - $(btn).addClass("disabled"); - $(btn).text("Deleting..."); - $(btn).parent().find(".delete-message").remove(); - patchew_api_do("delete-project-properties-by-prefix", - { project: current_project(), - prefix: prefix }) - .done(function (data) { - item.remove(); - }) - .fail(function (data, text, error) { - $(btn).removeClass("disabled"); - $(btn).text("Delete"); - info =3D $("
"); - info.html("Error: " + error); - info.insertBefore($(btn)); - }); + item.remove(); + confirm_leaving_page(true); } function enum_change(which) { val =3D $(which).val(); diff --git a/tests/patchewtest.py b/tests/patchewtest.py index ade039d..e58e5b9 100644 --- a/tests/patchewtest.py +++ b/tests/patchewtest.py @@ -127,11 +127,15 @@ class PatchewTestCase(dj_test.LiveServerTestCase): =20 def add_project(self, name, mailing_list=3D"", git_repo=3D""): p =3D Project(name=3Dname, mailing_list=3Dmailing_list, git=3Dgit_= repo or self.create_git_repo(name)) - p.save() push_repo =3D self.create_git_repo(name + "_push") - p.set_property("git.push_to", push_repo) - p.set_property("git.public_repo", push_repo) - p.set_property("git.url_template", push_repo) + p.config =3D { + "git": { + "push_to": push_repo, + "public_repo": push_repo, + "url_template": push_repo + } + } + p.save() return p =20 def api_login(self): diff --git a/tests/test_git.py b/tests/test_git.py index 09545df..0b9fa50 100755 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -22,9 +22,14 @@ class GitTest(PatchewTestCase): self.cli_login() self.repo =3D self.create_git_repo() self.p =3D self.add_project("QEMU", "qemu-devel@nongnu.org", git_r= epo=3Dself.repo) - self.p.set_property("git.push_to", self.repo) - self.p.set_property("git.public_repo", self.repo) - self.p.set_property("git.url_template", self.repo + " %t") + self.p.config =3D { + "git": { + "push_to": self.repo, + "public_repo": self.repo, + "url_template": self.repo + " %t" + } + } + self.p.save() self.PROJECT_BASE =3D '%sprojects/%d/' % (self.REST_BASE, self.p.i= d) =20 def cleanUp(self): diff --git a/tests/test_testing.py b/tests/test_testing.py index a5c22e1..2a01b7a 100755 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -17,11 +17,24 @@ from .patchewtest import PatchewTestCase, main =20 =20 def create_test(project, name, requirements=3D"", script=3D"#!/bin/bash\nt= rue"): - prefix =3D "testing.tests." + name + "." - project.set_property(prefix + "timeout", 3600) - project.set_property(prefix + "enabled", True) - project.set_property(prefix + "script", script) - project.set_property(prefix + "requirements", requirements) + testing =3D project.config.setdefault('testing', {}) + tests =3D testing.setdefault('tests', {}) + tests[name] =3D { + 'timeout': 3600, + 'enabled': True, + 'script': script, + 'requirements': requirements + } + project.save() + + +def create_requirement(project, name, script=3D"#!/bin/bash\ntrue"): + testing =3D project.config.setdefault('testing', {}) + requirements =3D testing.setdefault('requirements', {}) + requirements[name] =3D { + 'script': script, + } + project.save() =20 =20 class TestingTestCase(PatchewTestCase, metaclass=3Dabc.ABCMeta): @@ -219,13 +232,11 @@ class TesterTest(PatchewTestCase): create_test(self.p2, "b") =20 self.p3 =3D self.add_project("ALLOW", "qemu-devel@nongnu.org") - self.p3.set_property("testing.requirements.allow.script", - "#!/bin/sh\ntrue") + create_requirement(self.p3, 'allow', "#!/bin/sh\ntrue") create_test(self.p3, "c", "allow") =20 self.p4 =3D self.add_project("DENY", "qemu-devel@nongnu.org") - self.p4.set_property("testing.requirements.deny.script", - "#!/bin/sh\nfalse") + create_requirement(self.p4, 'deny', "#!/bin/sh\nfalse") create_test(self.p4, "d", "deny") =20 self.cli_login() @@ -388,7 +399,8 @@ class TestingDisableTest(PatchewTestCase): self.cli_login() self.cli_import('0013-foo-patch.mbox.gz') self.do_apply() - self.p1.set_property("testing.tests.a.enabled", False) + self.p1.config['testing']['tests']['a']['enabled'] =3D False + self.p1.save() out, err =3D self.check_cli(["tester", "-p", "QEMU", "--no-wait"]) self.assertNotIn("Project: QEMU\n", out) self.cli_logout() --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel