checkbox-converged-1.2.4/0000775000175000017500000000000012645002307015213 5ustar sylvainsylvaincheckbox-converged-1.2.4/confinement/0000775000175000017500000000000012643477065017537 5ustar sylvainsylvaincheckbox-converged-1.2.4/confinement/plainbox-confined-shell.qml0000664000175000017500000000747112643477065024767 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief QML standalone shell for confined tests */ import QtQuick 2.0 import Ubuntu.Components 0.1 import Ubuntu.Content 1.1 import io.thp.pyotherside 1.2 MainView { id: mainView width: units.gu(100) height: units.gu(75) // information and functionality passed to qml job component property var testingShell: { "name": "Checkbox-touch qml confined shell", "pageStack": pageStack, "python": py } property var activeTransfer; Arguments { id: args Argument { name: "job" help: "QML-native job to run" required: true valueNames: ["PATH"] } Argument { name: "checkbox-name" help: "Qualified name of Checkbox app to send results to" required: true valueNames: ["checkbox-touch-x.x.x"] } } Loader { id: loader anchors.fill: parent onStatusChanged: { if (loader.status === Loader.Error) { testDone({'outcome': 'crash'}); } } onLoaded: loader.item.testDone.connect(testDone) } Python { id: py Component.onCompleted: { addImportPath('./confinement/'); importModule('os', function() {}); importModule('plainbox_confined_shell', function() { loader.setSource(args.values['job'], {'testingShell': testingShell}); }); } } function testDone(res) { loader.active = false; endPage.visible = true; var transfer = checkboxPeer.request() py.call("plainbox_confined_shell.obj_to_file", [res, 'com.ubuntu.checkbox', 'res.json'], function(resJson) { console.log('Result file availble @ ' + resJson); mainView.activeTransfer = checkboxPeer.request() mainView.activeTransfer.items = [ contentItemComponent.createObject( mainView, {url: resJson})] mainView.activeTransfer.state = ContentTransfer.Charged; }); } Page { id: endPage visible: false anchors.fill: parent Label { anchors.fill: parent text: i18n.tr("Sending report - you should not see this page :-)") } } ContentPeer { id: checkboxPeer appId: args.values['checkbox-name'] contentType: ContentType.Documents handler: ContentHandler.Destination } Connections { target: mainView.activeTransfer onStateChanged: { if (mainView.activeTransfer.state === ContentTransfer.Finalized) { var resultFile = String(mainView.activeTransfer.items[0].url).replace( 'file://', ''); console.log("Transfer completed; removing result file"); py.call('os.unlink', [resultFile], function() { Qt.quit(); }); } } } Component { id: contentItemComponent ContentItem { } } PageStack { id: pageStack } } checkbox-converged-1.2.4/confinement/generate.py0000775000175000017500000000717712643477065021722 0ustar sylvainsylvain#!/usr/bin/env python3 # This file is part of Checkbox. # # Copyright 2015 Canonical Ltd. # Written by: # Maciej Kisielewski # # Checkbox is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, # as published by the Free Software Foundation. # # Checkbox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Checkbox. If not, see . import argparse import json import os import string import sys CONTENT_HUB = """{ "destination": [ "documents"], "source": [ "documents"], "share": [ "documents"] }""" APPARMOR = """{ "policy_groups": [ "networking", "webview", "content_exchange", "content_exchange_source" ], "policy_version": 1.2 }""" DESKTOP = """[Desktop Entry] Name=Checkbox-${partial_id} Comment=${partial_id} - confined test from Checkbox Exec=qmlscene -I lib/py/plainbox/data/plainbox-qml-modules/ -I providers/${provider_name}/data/ --checkbox-name=${full_checkbox_name} --job ../providers/${provider_name}/data/${qml_file} $$@ confinement/plainbox-confined-shell.qml Icon=checkbox-touch.svg Terminal=false Type=Application X-Ubuntu-Touch=true """ def generate_confinement(provider_name, partial_id, full_checkbox_name, qml_file): # generate content-hub file target_dir = os.path.join('data', 'confined') if not os.path.exists(target_dir): os.makedirs(target_dir) content_hub_path = os.path.join(target_dir, partial_id + '-ch.json') with open(content_hub_path, "wt") as f: f.write(CONTENT_HUB) # generate apparmor file apparmor_path = os.path.join(target_dir, partial_id + '.apparmor') with open(apparmor_path, "wt") as f: f.write(APPARMOR) # generate desktop file desktop_path = os.path.join(target_dir, partial_id + '.desktop') template = string.Template(DESKTOP) with open(desktop_path, "wt") as f: f.write(template.substitute( partial_id=partial_id, provider_name=provider_name, full_checkbox_name=full_checkbox_name, qml_file=qml_file)) base = 'providers/{provider_name}/data/confined/{partial_id}'.format( provider_name=provider_name, partial_id=partial_id) hook = { partial_id: { 'apparmor': base + '.apparmor', 'desktop': base + '.desktop', 'content-hub': base + '-ch.json', } } return hook def main(): parser = argparse.ArgumentParser( description="Generate confinement files for Checkbox") parser.add_argument('--checkbox_version', action='store', type=str) parser.add_argument('job_ids', nargs='+') args = parser.parse_args() checkbox_name = ("com.ubuntu.checkbox_checkbox-touch_" + args.checkbox_version) # check if current dir looks like a provider - very dumb heuristic if not os.path.exists('manage.py'): sys.exit("Current directory doesn't look like a plainbox provider") provider_name = os.path.split(os.getcwd())[-1] hooks = '' for job in args.job_ids: hook = generate_confinement( provider_name, job, checkbox_name, job + '.qml') hooks += json.dumps(hook, sort_keys=True, indent=4)[1:-1] print("Add following hooks to your checkbox-touch.manifest:") print(hooks) if __name__ == '__main__': main() checkbox-converged-1.2.4/confinement/plainbox_confined_shell.py0000664000175000017500000000260112643477065024760 0ustar sylvainsylvain# This file is part of Checkbox. # # Copyright 2015 Canonical Ltd. # Written by: # Maciej Kisielewski # # Checkbox is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, # as published by the Free Software Foundation. # # Checkbox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Checkbox. If not, see . import json import os def obj_to_file(obj, consumer_name, filename): """ Stringify object, write it to file and return path to the file. :param object: Object to be stringified :param consumer_name: Name of the app that will consume the generated file :param filename: Name of the file to write to (just the basename) :returns: Path to the written file """ s = json.dumps(obj) dir_path = os.path.join(os.environ['XDG_RUNTIME_DIR'], consumer_name) if not os.path.exists(dir_path): os.makedirs(dir_path) out_path = os.path.join(dir_path, filename) with open(out_path, "wt") as f: f.write(s) return out_path checkbox-converged-1.2.4/requirements/0000775000175000017500000000000012643477065017755 5ustar sylvainsylvaincheckbox-converged-1.2.4/requirements/deb-touch.txt0000664000175000017500000000001312643477065022362 0ustar sylvainsylvainxauth xvfb checkbox-converged-1.2.4/requirements/container-tests-touch-unit-tests0000775000175000017500000000067312643477065026270 0ustar sylvainsylvain#!/bin/sh # Ubuntu-touch will not be available on 12.04, yet whole checkbox stack is tested against 12.04 # Should 12.04 be detected, skip the tests of checkbox-touch app if grep 12.04 /etc/lsb-release; then echo "WARNING NOT TESTING UNSUPPORTED RELEASE"; exit 0; fi # Run checkbox-touch with fake X # xvfb-run --server-args='-screen 0 1024x768x24 -ac +extension GLX +render -noreset' make check # FIXME: fix dependencies and re-enable this checkbox-converged-1.2.4/.excludes0000664000175000017500000000000012643477065017035 0ustar sylvainsylvaincheckbox-converged-1.2.4/components/0000775000175000017500000000000012645002307017400 5ustar sylvainsylvaincheckbox-converged-1.2.4/components/TestVerificationPage.qml0000664000175000017500000000540012643477065024210 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Test verification page This page asks user whether the action was completed successfully See design document at: http://goo.gl/ghR9wL */ import QtQuick 2.0 import Ubuntu.Components 1.1 import QtQuick.Layouts 1.1 import Ubuntu.Components.Popups 0.1 import "actions" Page { id: testVerification property var test: { "name": "", "verificationDescription": "", "test_number": 0, "tests_count": 0} signal testDone(var test); objectName: "testVerificationPage" title: i18n.tr("Verification") head { actions: [ AddCommentAction {}, SkipAction {} ] } TestPageBody { header: test["name"] body: test["verificationDescription"] Button { id: showOutputButton objectName: "showOutputButton" visible: ((test["command"]) ? true : false) color: "white" Layout.fillWidth: true text: "Output" onClicked: { pageStack.push(commandOutputPage); } } LatchButton { id: passButton objectName: "passButton" unlatchedColor: UbuntuColors.green Layout.fillWidth: true // TRANSLATORS: this string is on a button that marks the given test as passed text: i18n.tr("Pass") onLatchedClicked: { test["outcome"] = "pass"; latchingTestDone(); } } LatchButton { id: failButton objectName: "failButton" unlatchedColor: UbuntuColors.red Layout.fillWidth: true // TRANSLATORS: this string is on a button that marks the given test as failed text: i18n.tr("Fail") onLatchedClicked: { test["outcome"] = "fail"; latchingTestDone(); } } } function latchingTestDone() { passButton.state = "latched"; failButton.state = "latched"; testDone(test); } } checkbox-converged-1.2.4/components/LatchButton.qml0000664000175000017500000000434012643477065022362 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 /*! \brief Latch button. \inherits Button This widget is a Button with latch functionality. I.e. once the button is clicked it cannot be clicked again until 'unlatch' method is called. The button also changes color depending on which state it is in. */ Button { id: root /*! Gets signalled when button is clicked while in 'unlatched' state */ signal latchedClicked /*! Call this method to change the state to 'unlatched' */ function unlatch() { state = "unlatched" } /*! Color of the button while in 'unlatched' state */ property var unlatchedColor: UbuntuColors.green /*! Color of the button while in 'latched' state */ property var latchedColor: UbuntuColors.warmGrey /*! Read-only property informing if button is currently latched. */ readonly property bool isLatched: state === "latched" state: "unlatched" states: [ State { name: "unlatched" PropertyChanges { target: root; color: unlatchedColor } PropertyChanges { target: root; enabled: true } }, State { name: "latched" PropertyChanges { target: root; color: latchedColor } PropertyChanges { target: root; enabled: false } } ] onClicked: { if (state == "unlatched") { state = "latched" latchedClicked(); } } } checkbox-converged-1.2.4/components/actions/0000775000175000017500000000000012643477065021057 5ustar sylvainsylvaincheckbox-converged-1.2.4/components/actions/SkipAction.qml0000664000175000017500000000362412643477065023643 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Action component for skipping a test. */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import "../ConfirmationLogic.js" as ConfirmationLogic Action { id: skipAction objectName: "skip" iconName: "media-seek-forward" text: i18n.tr("Skip") onTriggered: { var confirmationOptions = { question : i18n.tr("Do you really want to skip this test?"), remember : true, } ConfirmationLogic.confirmRequest(mainView, confirmationOptions, function(res) { var currentTest = test; if (res) { commentsDialog.commentDefaultText = test["comments"] || ""; var handler = function(comment) { currentTest["comments"] = comment; currentTest["outcome"] = "skip"; commentsDialog.commentAdded.disconnect(handler); testDone(currentTest); }; commentsDialog.commentAdded.connect(handler); PopupUtils.open(commentsDialog.dialogComponent); } }); } } checkbox-converged-1.2.4/components/actions/AddCommentAction.qml0000664000175000017500000000232312643477065024743 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Action component for adding a comment. */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 Action { id: addCommentAction iconName: "note-new" text: i18n.tr("Add comment") onTriggered: { commentsDialog.commentDefaultText = test["comments"] || ""; commentsDialog.commentAdded.connect(function(comment) { test["comments"] = comment; }); PopupUtils.open(commentsDialog.dialogComponent); } } checkbox-converged-1.2.4/components/ConfirmationLogic.js0000664000175000017500000000127012643477065023363 0ustar sylvainsylvain.import Ubuntu.Components.Popups 1.0 as Popups function confirmRequest(caller, options, continuation) { // if the question was answered before and user selected to // remember their selection - 'returning' true if (mainView.appSettings[options.question]) { continuation(true); return; } var popup = Qt.createComponent(Qt.resolvedUrl("ConfirmationDialog.qml")).createObject(caller); popup.withRemember = options.remember; popup.question = options.question; popup.answer.connect(function(result, remember) { mainView.appSettings[options.question] = remember; continuation(result); }); Popups.PopupUtils.open(popup.dialog); } checkbox-converged-1.2.4/components/QmlNativePage.qml0000664000175000017500000000650712643477065022637 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page for Qml native test */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import QtQuick.Layouts 1.1 import "actions" Page { id: qmlNativePage property var test: { "name": "", "description": "", "test_number": 0, "tests_count": 0} signal testDone(var test); objectName: "qmlNativePage" title: i18n.tr("Test Description") Object { id: testingShell property string name: "Checkbox-touch qml shell" property alias pageStack: qmlNativePage.pageStack property string sessionDir: app.sessionDir property var python: app.py function getTest() { return test; } } head { actions: [ AddCommentAction {}, SkipAction {} ] } TestPageBody { header: test["name"] body: test["description"] LatchButton { objectName: "continueButton" color: UbuntuColors.green Layout.fillWidth: true text: i18n.tr("Start the test") onClicked: { pageStack.pop(); // pop the description page // altough there shouldn't be anything on the page stack // dump it to savedStack and clear page stack, // to let qml job use fresh playground var savedStack = []; while(pageStack.depth) { savedStack.push(pageStack.currentPage); pageStack.pop(); } // prepare page with the test var testItemComponent = Qt.createComponent(Qt.resolvedUrl(test['qml_file'])); if (testItemComponent.status == Component.Error) { console.error("Error creating testItemComponent. Possible cause: Problem with job's qml file. Error:", testItemComponent.errorString()); test['outcome'] = 'fail'; testDone(test); return; } var testItem = testItemComponent.createObject(mainView, {"testingShell": testingShell}); testItem.testDone.connect(function(testResult) { test['outcome'] = testResult['outcome']; test['qmlResult'] = testResult; pageStack.clear(); // clean test's left-overs from the stack while(savedStack.length) { pageStack.push(savedStack.pop()); } testItem.destroy(); testDone(test); }); } } } } checkbox-converged-1.2.4/components/ConfirmationDialog.qml0000664000175000017500000000611412643477065023704 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 /*! \brief Skip-confirmation dialog. \inherits Item This component is a confirmation dialog with an option to remember selected option. It comes with a Component object encosing dialog so callers have to call PopupUtils.open() only. */ Item { id: confirmationDialog objectName: "confirmationDialog" /*! Gets signalled user selects an option */ signal answer(bool confirmation, bool remember) /*! `dialog` alias helps use the component containing dialog */ property alias dialog : dialogComponent /*! Text to display in confimation dialog */ property string question : i18n.tr("Are you sure?") /*! Presents option to remember choice */ property bool withRemember: false width: units.gu(80) height: units.gu(80) Component { id: dialogComponent Dialog { id: dlg objectName: "dialog" modal: true // Screen behind the dialog will be greyed-out title: question Button { text: i18n.tr("YES") objectName: "yesButton" color: UbuntuColors.green onClicked: { answer(true, checkBox.checked); PopupUtils.close(dlg); } } Button { text: i18n.tr("NO") objectName: "noButton" color: UbuntuColors.red onClicked: { answer(false, checkBox.checked); PopupUtils.close(dlg); } } Row { visible: withRemember CheckBox { id: checkBox } Label { text: i18n.tr("Do not ask me this question again") anchors.verticalCenter: parent.verticalCenter MouseArea{ // This MouseArea helps trigger checkbox changes // when user taps on the label anchors.fill: parent onClicked: { checkBox.checked = !checkBox.checked } } } } } } } checkbox-converged-1.2.4/components/AutomatedTestPage.qml0000664000175000017500000000425312643477065023516 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import QtQuick.Layouts 1.1 /*! \brief Page for Automated Test This page shows test name and description of an Automated Test See design document at: http://goo.gl/He06jc */ Page { id: automatedTestPage objectName: "automatedTestPage" property var test: { "name": "", "description": "", "test_number": 0, "tests_count": 0} title: i18n.tr("Automated test") visible: false TestPageBody { header: test["name"] body: test["description"] } ColumnLayout { anchors { bottom: parent.bottom left: parent.left right: parent.right bottomMargin: units.gu(4) } ActivityIndicator { Layout.alignment: Qt.AlignHCenter id: activity implicitHeight: units.gu(6) implicitWidth: units.gu(6) } Button { id: showOutputButton objectName: "showOutputButton" visible: ((test["command"]) ? true : false) && activity.running color: "white" Layout.fillWidth: true text: "Output" onClicked: { pageStack.push(commandOutputPage); } } } function startActivity() { activity.running = true; } function stopActivity() { activity.running = false; } Component.onCompleted: { startActivity(); } } checkbox-converged-1.2.4/components/WebViewer.qml0000664000175000017500000000176212643477065022037 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Chris Wayne * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Web 0.2 import Ubuntu.Components 1.1 Page { title: i18n.tr("Test Report") visible: false property var uri objectName: "webviewerpage" WebView { id: wv anchors.fill: parent url: "" } onUriChanged: { wv.url = uri; wv.reload(); } }checkbox-converged-1.2.4/components/WelcomePage.qml0000664000175000017500000000631712643477065022331 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Zygmunt Krynicki * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 Page { id: welcomePage objectName: "welcomePage" signal startTestingTriggered(); signal aboutClicked(); property alias welcomeText: welcomeText.text title: i18n.tr("System Testing") visible: false function enableButton() { startTestButton.unlatch(); state = "loaded"; } head { actions: [ Action { id: continueAction iconName: "info" text: i18n.tr("About") onTriggered: aboutClicked() } ] } Label { id: welcomeText anchors { top: parent.top left: parent.left right: parent.right bottom: startTestButton.top } text: "" fontSize: "large" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere } state: "loading" states: [ State { name: "loading" PropertyChanges { target: startTestButton; enabled: false; color: UbuntuColors.warmGrey; text: i18n.tr("Checkbox is loading...") } PropertyChanges { target: loadingSpinner; running: true} }, State { name: "loaded" PropertyChanges { target: startTestButton; enabled: true; color: UbuntuColors.green; text: i18n.tr("Start testing")} PropertyChanges { target: loadingSpinner; running: false} } ] transitions: Transition { from: "loading"; to: "loaded" ColorAnimation { duration: 250 } } ActivityIndicator { id: loadingSpinner anchors { bottom: startTestButton.top left: parent.left right: parent.right bottomMargin: units.gu(4) } implicitHeight: units.gu(6) implicitWidth: units.gu(6) } LatchButton { id: startTestButton objectName: "startTestButton" anchors { left: parent.left right: parent.right bottom: parent.bottom topMargin: units.gu(3) bottomMargin: units.gu(3) leftMargin: units.gu(1) rightMargin: units.gu(1) } unlatchedColor: UbuntuColors.green text: i18n.tr("Start testing") onLatchedClicked: startTestingTriggered(); } } checkbox-converged-1.2.4/components/InteractIntroPage.qml0000664000175000017500000000577112643477065023526 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Intro page for tests requiring user interation This page shows test name and description. When the test is run, page displays activity indicator See design document at: http://goo.gl/ghR9wL */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import QtQuick.Layouts 1.1 import "actions" Page { id: userInteractVerifyIntroPage objectName: "userInteractVerifyIntroPage" property var test: { "name": "", "description": "", "test_number": 0, "tests_count": 0} signal testStarted(); signal testDone(var test); function stopActivity() { state = "idle" startTestButton.unlatch() } title: i18n.tr("Test Description") head { actions: [ AddCommentAction {}, SkipAction { id: skipAction } ] } state: "idle" states: [ State { name: "idle" PropertyChanges { target: activity; running: false } PropertyChanges { target: skipAction; visible: true } }, State { name: "testing" PropertyChanges { target: activity; running: true } PropertyChanges { target: skipAction; visible: false } } ] TestPageBody { header: test["name"] body: test["description"] ActivityIndicator { id: activity Layout.alignment: Qt.AlignHCenter implicitHeight: units.gu(6) implicitWidth: units.gu(6) } Button { id: showOutputButton objectName: "showOutputButton" visible: ((test["command"]) ? true : false) && (userInteractVerifyIntroPage.state == "testing") color: "white" Layout.fillWidth: true text: "Output" onClicked: { pageStack.push(commandOutputPage); } } LatchButton { id: startTestButton objectName: "startTestButton" unlatchedColor: UbuntuColors.green Layout.fillWidth: true text: i18n.tr("Start the test") onLatchedClicked: { userInteractVerifyIntroPage.state = "testing" testStarted(); } } } } checkbox-converged-1.2.4/components/InputDialog.qml0000664000175000017500000000414512643477065022355 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 /*! \brief Simple input dialog. \inherits Item */ Item { id: inputDialog /*! This alias aids the process of popping up the dialog. Usage: PopupUtils.open(inputDialog.dialogComponent); */ property alias dialogComponent: component property string prompt: "" signal textEntered(string text) signal cancelClicked() Component { id: component Dialog { id: dialog title: prompt modal: true TextField { id: textBox onAccepted: okButton.clicked(text) } Button { id: okButton text: i18n.tr("OK") color: UbuntuColors.green onClicked: { PopupUtils.close(dialog); textEntered(textBox.text); textBox.text = ""; } } Button { text: i18n.tr("Cancel") color: UbuntuColors.red onClicked: { textBox.text = ""; PopupUtils.close(dialog); cancelClicked(); } } Component.onCompleted: { textBox.forceActiveFocus(); } } } } checkbox-converged-1.2.4/components/AboutPage.qml0000664000175000017500000000611312645002025021760 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page with version and copyright information This page shows version information and contents of AUTHORS file located in top chekckbox-touch directory */ import QtQuick 2.0 import Ubuntu.Components 1.1 import QtQuick.Layouts 1.1 Page { id: aboutPage property var versionInfo : { "checkbox_touch" : "0.0", "plainbox" : "0.0" } onVersionInfoChanged: body.generateVersionText() title: i18n.tr("About") ColumnLayout { spacing: units.gu(3) anchors { fill: parent topMargin: units.gu(3) bottomMargin: units.gu(3) leftMargin: units.gu(1) rightMargin: units.gu(1) } Flickable { id: flickable Layout.fillHeight: true Layout.fillWidth: true contentHeight: body.height contentWidth: body.width flickableDirection: Flickable.VerticalFlick clip: true Text { id: body Layout.fillHeight: true Layout.fillWidth: true width: flickable.width wrapMode: Text.WrapAtWordBoundaryOrAnywhere textFormat: Text.RichText text: versionString + copyrightString //custom codde for generating body content property string versionString: ""; property string copyrightString: ""; function generateVersionText() { body.versionString = "

Checkbox Touch

" + i18n.tr("version: ") + aboutPage.versionInfo["checkbox_touch"] + "
" + i18n.tr("Plainbox version: ") + aboutPage.versionInfo["plainbox"] + "

"; } } } Button { text: i18n.tr("Close") Layout.fillWidth: true onTriggered: pageStack.pop() color: UbuntuColors.green } } Component.onCompleted: { var request = new XMLHttpRequest() request.open('GET', '../AUTHORS') request.onreadystatechange = function(event) { if (request.readyState == XMLHttpRequest.DONE) { body.copyrightString = request.responseText; } } request.send() body.generateVersionText(); } } checkbox-converged-1.2.4/components/PieChart.qml0000664000175000017500000000424612643477065021637 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Sylvain Pineau * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \Pie Chart component Using the canvas element. */ import QtQuick 2.0 Canvas { id: canvas property var chartData; property var borderColor: "#ECECEC"; property int borderWidth: 1; property real progress: 0; property alias duration: animation.duration; onPaint: { var centreX = width / 2; var centreY = height / 2; var size = Math.min(width, height) / 2; var total = 0; for (var i = 0; i < chartData.length; i++) total += chartData[i].value; var arcTotal = -Math.PI / 2; var ctx = getContext("2d"); ctx.reset(); for (var i = 0; i < chartData.length; i++) { var arc = progress * chartData[i].value / total * Math.PI / 50; ctx.beginPath(); ctx.fillStyle = chartData[i].color; ctx.moveTo(centreX, centreY); ctx.arc(centreX, centreY, size, arcTotal, arc + arcTotal); ctx.lineTo(centreX, centreY); ctx.fill(); ctx.lineWidth = borderWidth; ctx.strokeStyle = borderColor; ctx.stroke(); arcTotal += arc; } animation.start(); } onProgressChanged: { requestPaint(); } function repaint() { progress = 0; animation.start(); } PropertyAnimation { id: animation; target: canvas; property: "progress"; to: 100; duration: 1000; // milliseconds } } checkbox-converged-1.2.4/components/SelectionPage.qml0000664000175000017500000001551012643477065022656 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import QtQuick.Layouts 1.1 import Ubuntu.Components 1.1 import Ubuntu.Components.ListItems 0.1 as ListItem /*! \brief Selection page This page lets the user select items from the list. Items can be grouped together. See design document here: http://goo.gl/vkqvIC */ Page { signal selectionDone(var selected_id_list) property string continueText: i18n.tr("Continue") readonly property alias model: selectionModel property bool onlyOneAllowed: false property bool emptyAllowed: false property bool largeBuffer: false visible: false flickable: null property var selectedCount : 0 state : selectedCount > 0 ? "nonempty selection" : "empty selection" // A function that needs to be called after changes are done to the model // to re-count number of selected items on the list function modelUpdated() { selectedCount = 0; for (var i=0; i < selectionModel.count; i++) { if (selectionModel.get(i).mod_selected) { selectedCount++; } } } function gatherSelection() { var selected_id_list = []; for (var i=0; i 0) { // clear other selections deselectAll(); } selectionModel.setProperty(index, 'mod_selected', !checked); selectedCount += checked ? 1 : -1; } } onClicked: checkBox.clicked() } section.property: "mod_group" // NOTE: this is a model reference section.criteria: ViewSection.FullString section.delegate: sectionHeading snapMode: ListView.SnapToItem } LatchButton { id: continueButton objectName: "continueButton" Layout.fillWidth: true text: continueText unlatchedColor: UbuntuColors.green onLatchedClicked: gatherSelection() } } } checkbox-converged-1.2.4/components/CommandOutputPage.qml0000664000175000017500000000541712643477065023535 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page displaying an output of running command */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import QtQuick.Layouts 1.1 Page { id: commandOutputPage objectName: "commandOutputPage" property var bufferedIO; function addText(text) { bufferedIO += text } function clear() { bufferedIO = ""; textArea.text = ""; } title: i18n.tr("Command output") head { actions: [ Action { id: skipAction objectName: "copyOutputAction" iconName: "edit-copy" // TRANSLATORS: a verb (call to action) text: i18n.tr("Copy") onTriggered: Clipboard.push(mimeData) } ] } Timer { id: timer interval: 300 running: false repeat: true onTriggered: { textArea.text += bufferedIO bufferedIO = "" } } ColumnLayout { spacing: units.gu(1) anchors.fill: parent anchors.margins: units.gu(3) TextArea { id: textArea objectName: "textArea" Layout.fillHeight: true Layout.fillWidth: true readOnly: true font.family: "Ubuntu Mono" wrapMode: TextEdit.WrapAnywhere MimeData { id: mimeData text: textArea.text } onTextChanged: { cursorPosition = text.length; } } Button { Layout.fillWidth: true // TRANSLATORS: This is a label on the button that goes back a page text: i18n.tr("Back") onClicked: pageStack.pop() } } onVisibleChanged: { // Pop-over should be displayed only when page becomes visible (in practice - when it's pushed to the pageStack) if (visible == true) { timer.running = true; } else { timer.running = false; } } } checkbox-converged-1.2.4/components/ManualIntroPage.qml0000664000175000017500000000333412643477065023163 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page for Manual test - introduction step This page shows test name and description of an Manual test. See design document at: http://goo.gl/vrD9yn */ import QtQuick 2.0 import Ubuntu.Components 1.1 import QtQuick.Layouts 1.1 import Ubuntu.Components.Popups 0.1 import "actions" Page { id: manualIntroPage objectName: "manualIntroPage" property var test: { "name": "", "description": "", "test_number": 0, "tests_count": 0} signal continueClicked(); signal testDone(var test); title: i18n.tr("Test Description") head { actions: [ AddCommentAction {}, SkipAction {} ] } TestPageBody { header: test["name"] body: test["description"] Button { objectName: "continueButton" color: UbuntuColors.green Layout.fillWidth: true text: i18n.tr("Start the test") onClicked: { continueClicked(); } } } } checkbox-converged-1.2.4/components/PasswordDialog.qml0000664000175000017500000000522612643477065023061 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 /*! \brief Password prompt dialog. \inherits Item This component is a prompt for user's password. */ Item { id: passwordDialog /*! This alias aids the process of popping up the dialog. Usage: PopupUtils.open(passwordDialog.dialogComponent); */ property alias dialogComponent: component signal passwordEntered(string password) signal dialogCancelled Component { id: component Dialog { id: dialog title: i18n.tr("Enter password") modal: true TextField { id: passwordBox objectName: "passwordBox" placeholderText: i18n.tr("password") echoMode: TextInput.Password onAccepted: okButton.clicked(text) } Button { id: okButton objectName: "okButton" text: i18n.tr("OK") color: UbuntuColors.green onClicked: { PopupUtils.close(dialog); // once qml pam authentication goes live, we might want to // check if the password is correct in some QML-ish manner passwordEntered(passwordBox.text); passwordBox.text = ""; } } Button { objectName: "cancelButton" text: i18n.tr("Cancel") color: UbuntuColors.red onClicked: { passwordBox.text = ""; PopupUtils.close(dialog); dialogCancelled(); } } Component.onCompleted: { // let user type in password without tapping on // the text field passwordBox.forceActiveFocus(); } } } } checkbox-converged-1.2.4/components/TestPageBody.qml0000664000175000017500000000455612643477065022476 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief ColumnLayout components with header and body labels already included * and some common UX properties already set. */ import QtQuick 2.0 import QtQuick.Layouts 1.1 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 ColumnLayout { /* this property is the text to be shown in the top label */ property var header: "" /* this property is the text that's used as the main body of the page. It * will be wrapping itself, filling width of the page (column), and will be * flickable */ property var body: "" /* set fullHeightBody to false if you want to have control over layout in * the code that uses TestPageBody. You might want for instance have one * item that dominates the page. * If left set as true the layout is split evenly between children. */ property var fullHeightBody: true spacing: units.gu(3) anchors { fill: parent topMargin: units.gu(3) bottomMargin: units.gu(3) leftMargin: units.gu(1) rightMargin: units.gu(1) } Label { objectName: "headerLabel" fontSize: "large" Layout.fillWidth: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere text: header font.bold: true } Flickable { Layout.fillWidth: true Layout.fillHeight: fullHeightBody contentHeight: bodyLabel.contentHeight flickableDirection: Flickable.VerticalFlick clip: true Label { id: bodyLabel fontSize: "medium" anchors.fill: parent wrapMode: Text.WrapAtWordBoundaryOrAnywhere text: body } } } checkbox-converged-1.2.4/components/UserInteractSummaryPage.qml0000664000175000017500000000576412643477065024731 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page for User-Interact test - summary step This page shows result of automated verification of a test. See design document at: http://goo.gl/6pNAFE */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import QtQuick.Layouts 1.1 import "actions" Page { id: userInteractSummary objectName: "userInteractSummary" property var test: { "name": "", "outcome": "pass", "test_number": 0, "tests_count": 0} signal testDone(var test); title: i18n.tr("Verification") head { actions: [ AddCommentAction {}, SkipAction {} ] } TestPageBody { header: test["name"] fullHeightBody: false Row { Layout.fillWidth: true Label { fontSize: "large" Layout.fillHeight: true // TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" text : i18n.tr("This test ") wrapMode: Text.WrapAtWordBoundaryOrAnywhere } Rectangle { height: resultLabel.height width: resultLabel.width + units.gu(1) Layout.alignment: Qt.AlignCenter radius: 2 color: test["outcome"] == "pass" ? UbuntuColors.green : UbuntuColors.red Label { id: resultLabel height: paintedHeight fontSize: "large" wrapMode: Text.WrapAtWordBoundaryOrAnywhere color: "white" text : test["outcome"] == "pass" ? i18n.tr("PASSED") : i18n.tr("FAILED") anchors.centerIn: parent } } } Label { fontSize: "large" Layout.fillHeight: true Layout.fillWidth: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere text : i18n.tr("You can go back to rerun the test or continue to the next test.") } Button { color: UbuntuColors.green objectName: "continueButton" Layout.fillWidth: true text: i18n.tr("Continue") onClicked: { testDone(test); } } } } checkbox-converged-1.2.4/components/PythonObjectRef.qml0000664000175000017500000000565212643477065023207 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 QtObject { id: pythonObjectRef // Reference to pyotherside's Python object property var py: null // PyObjectRef to the object property var object: null // Signal sent when the object reference is ready to use signal objectReady() // Creation method name that were used to get the reference to the object property var creationMethodName: null function construct(creationMethodName, args) { if (!py) { console.error("Trying to get reference to python object without py initiated!"); } else { console.info("Getting reference to python object via " + creationMethodName); py.call(creationMethodName, args, function(result) { if (!result) { var msg = "Object construction failed. " + creationMethodName + " did not return a valid object"; console.error(msg); throw msg; } else { object = result; pythonObjectRef.creationMethodName = creationMethodName; objectReady(); } }); } } /** Call a method on this object */ function invoke(func, args, callback) { if (py !== null && object !== null) { console.log("invoking " + func + " on object created with" + pythonObjectRef.creationMethodName + ", with args: " + JSON.stringify(args) + " ..."); var callable = py.getattr(object, func); if (!callable) { console.log("Unable to invoke " + func + " on object " + JSON.stringify(pythonObjectRef)); throw "trying to invoke inexistent method" } py.call(callable, args, function(response) { console.log(func + " on object created with" + pythonObjectRef.creationMethodName + ", with args: " + JSON.stringify(args) + " returned: " + JSON.stringify(response)); callback(response); }); } else { console.error("Unable to invoke " + func + " on object " + JSON.stringify(pythonObjectRef)); throw "invoke called without py initiated and/or object constructed"; } } } checkbox-converged-1.2.4/components/CbtDialog.qml0000664000175000017500000000546512643477065021774 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import QtQuick.Layouts 1.1 /*! \brief Common dialog popup. \inherits Item This component is a general purpose message+buttons dialog. It should be used through API in CbtDialogLogic.js. By default every button in the dialog will close it. Typical usage: CbtDialog.showDialog(main, i18n.tr("Operation succedded")); CbtDialog.showDialog(main, i18n.tr("Do you want to proceed"), [ {"text": i18n.tr("OK"), "color": "green", "onClicked": function() {console.log("Clicked OK");} }, {"text": i18n.tr("Cancel"), "color": "red", "onClicked": function() {console.log("Clicked cancel");} }, ]); */ Item { id: dialog property var buttons: [] property alias dialog: dialogComponent property string label: "" Component { id: dialogComponent Dialog { id: dlg title: i18n.tr("") modal: true ColumnLayout { id: layout Label { text: label horizontalAlignment: Text.AlignHCenter Layout.fillWidth: true width: parent.width wrapMode: Text.WrapAtWordBoundaryOrAnywhere } } Component { /* This component is necessary for dynamic button creation below */ id: btnComponent Button { onClicked: { PopupUtils.close(dlg); } } } Component.onCompleted: { for (var b in buttons) { var newButtonProps = buttons[b]; newButtonProps["Layout.fillWidth"] = true; var newButton = btnComponent.createObject(layout, newButtonProps); if (buttons[b]['onClicked']) newButton.onClicked.connect(buttons[b]['onClicked']); } } } } } checkbox-converged-1.2.4/components/CheckboxTouchApplication.qml0000664000175000017500000001753512643477065025062 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014-2015 Canonical Ltd. * * Authors: * - Zygmunt Krynicki * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import "ErrorLogic.js" as ErrorLogic PythonObjectRef { id: app // Version of the application property string applicationVersion // Version of the plainbox library property string plainboxVersion // path to session storage directory property string sessionDir // Signal sent when the application becomes ready signal appReady(); // Signal sent when a session becomes ready signal sessionReady() // Create a new session // // Starts session in plainbox and runs all necessary setup actions. // Calling this function will signal sessionReady() once it's finished // doing setup. function startSession() { request("start_session", [], function(result) { sessionDir = result['session_dir']; sessionReady(); }, function(error) { console.error("Unable to start session: " + error); ErrorLogic.showError(mainView, i18n.tr("Could not start a session. Reason:\n" + error), Qt.quit, i18n.tr("Quit")); }); } function resumeSession(rerunLastTest, continuation) { request("resume_session", [rerunLastTest], function(result) { if (!result["session_id"]) { pageStack.pop(); ErrorLogic.showError(mainView, i18n.tr("Could not resume session"), function() { startSession(); return; }, i18n.tr("Start new session")); } else { sessionReady(); continuation(); } }, function(error) { console.error("Unable to resume session: " + error); }); } function clearSession(continuation) { request("clear_session", [], continuation, function(error) { console.error("Unable to clear session: " + error); }); } function isSessionResumable(continuation) { request("is_session_resumable", [], continuation, function(error) { console.error("Unable to check session resumability"); }); } function getTestplans(continuation) { request("get_testplans", [], continuation, function(error) { console.error("Unable to get testplans"); }); } function rememberTestplan(testplan, continuation) { var handleResult = function(result) { sessionDir = result['session_dir']; continuation(); } request("remember_testplan", [testplan], handleResult, function(error) { console.error("Unable to save testplan selection"); }); } function getCategories(continuation) { request("get_categories", [], continuation, function(error) { console.error("Unable to get categories"); }); } function rememberCategorySelection(categories, continuation) { request("remember_categories", [categories], continuation, function(error) { console.error("Unable to save category selection"); }); } function getTests(continuation) { request("get_available_tests", [], continuation, function(error) { console.error("Unable to get tests"); }); } function rememberTestSelection(tests, continuation) { request("remember_tests", [tests], continuation, function(error) { console.error("Unable to save test selection"); }); } function getNextTest(continuation) { request("get_next_test", [], continuation, function(error) { console.error("Unable to get next test"); }); } function registerTestResult(test, continuation) { request("register_test_result", [test], continuation, function(error) { console.error("Unable to save test result"); }); } function runTestActivity(test, continuation) { request("run_test_activity", [test], continuation, function(error) { console.error("Unable to run test activity"); }); } function getRerunCandidates(continuation) { request("get_rerun_candidates", [], continuation, function(error) { console.error("Unable to get rerun candidates"); }); } function getResults(continuation) { request("get_results", [], continuation, function(error) { console.error("Unable to get test results"); }); } function exportResults(output_format, option_list, continuation) { request("export_results", [output_format, option_list], continuation, function(error) { console.error("Unable to export test results"); }); } function submitResults(config, continuation, continuation_error) { request("submit_results", [config], continuation, function(error) { console.error("Unable to submit test results"); continuation_error(error); }); } function dropPermissions(appId, services, continuation, continuation_error) { request("drop_permissions", [appId, services], continuation, function(error) { console.error("Unable to remove permissions"); if (continuation_error) continuation_error(error); }); } function rememberPassword(password, continuation) { // using low-level py.call() to 'silently' pass password string through pyotherside var callable = py.getattr(object, "remember_password"); if (!callable) { console.error("Unable to invoke remember_password!"); throw "trying to invoke not existing method"; } py.call(callable, [password], function(response) { continuation(response); }); } // A wrapper around invoke() that works with the @view decorator. The fn_ok // and fn_err are called on a normal reply and on error, respectively. function request(name, args, fn_ok, fn_err) { invoke(name, args, function(response) { if (response.code == 200) { fn_ok(response.result); } else { fn_err(response.error); } }); } // Internal handler that triggers a call to python to query for runtime and // application versions. onObjectReady: { request("load_providers", [appSettings["providersDir"]], function(result) { request("get_version_pair", [], function(result) { app.applicationVersion = result.application_version; app.plainboxVersion = result.plainbox_version; appReady(); }, function(error) { console.error("Unable to query for version: " + error); }); }, function(error) { console.error("Unable to load providers: " + error); ErrorLogic.showError(mainView, i18n.tr("No providers available!"), Qt.quit); }); } } checkbox-converged-1.2.4/components/CommentsDialog.qml0000664000175000017500000000441212643477065023040 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 /*! \brief Comment addition dialog. \inherits Item This component is a prompt for user comments regarding test. */ Item { id: commentsDialog /*! `dialogComponent` alias shortens the code that launches this dialog */ property alias dialogComponent: component /*! Use this property to populate the textArea that the user will write in. It's useful when the comment is only updated - you can use the previous input. */ property var commentDefaultText: "" /*! Gets signalled when user taps 'done' */ signal commentAdded(string comment) Component { id: component Dialog { id: dialog title: i18n.tr("Add comment") modal: true TextArea { id: commentText objectName: "commentText" } Button { id: doneButton objectName: "doneButton" text: i18n.tr("Done") color: UbuntuColors.green onClicked: { PopupUtils.close(dialog); commentAdded(commentText.text); commentText.text = ""; } } Component.onCompleted: { commentText.text = commentDefaultText commentText.cursorPosition = commentText.text.length; commentText.forceActiveFocus(); } } } } checkbox-converged-1.2.4/components/ErrorDialog.qml0000664000175000017500000000414312643477065022345 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 /*! \brief Error message popup. \inherits Item This component is an error reporting dialog. To use this component call showError() from ErrorLogic.js Typical usage is: import "components/ErrorLogic.js" as ErrorLogic (...) ErrorLogic.showError(mainView, "There was something wrong", Qt.quit) */ Item { id: errorDialog /*! Gets signalled when user taps on the button */ signal done() property string buttonLabel: i18n.tr("Um, OK") /*! `dialog` alias helps use the component containing dialog */ property alias dialog: dialogComponent /*! Text to display in the error dialog */ property string errorMessage: i18n.tr("Error encountered") Component { id: dialogComponent Dialog { id: dlg title: i18n.tr("Error encountered") Label { text: errorMessage width: parent.width wrapMode: Text.WrapAtWordBoundaryOrAnywhere } Button { id: button text: errorDialog.buttonLabel color: UbuntuColors.red onClicked: { done(); PopupUtils.close(dlg); } } } } } checkbox-converged-1.2.4/components/ProgressBox.qml0000664000175000017500000000414112643477065022407 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import QtQuick.Layouts 1.1 /*! \brief Progress Box. \inherits Item This widget is a ProgressBar-like item with some changes compared to ProgressBar found in Ubuntu Components. This one uses layouts, so it can fills the width of the components it is placed in. ProgressBox can display an information about the progress prefixes by text set in `interlude` var. If the box is to be narrower then the space required by full text, interlude is ommited. The formatting of the text is as follows: "$interlude $vlaue / $maximumValue" */ Item { id: progressBox /*! Value to be used when filling progress bar. */ property alias value: progressBar.value /*! Progress bar is entirely when value reaches maximumValue */ property real maximumValue: 100 implicitWidth: units.gu(38) implicitHeight: units.gu(0.3) StyledItem { id: progressBar anchors.fill: parent property real value: 50 property real maximumValue: progressBox.maximumValue property real minimumValue: 0 property bool showProgressPercentage: false // for compability with underlaying styling property bool indeterminate: false // for compability with underlaying styling style: Theme.createStyleComponent("ProgressBarStyle.qml", progressBar) } } checkbox-converged-1.2.4/components/ErrorLogic.js0000664000175000017500000000137712643477065022034 0ustar sylvainsylvain.import Ubuntu.Components.Popups 1.0 as Popups function showError(caller, errorMessage, continuation, finalLabel) { // stackDepth keeps track of how many popups are stacked on the screen // we need this so continuation is called only if the last (the bottom one) popup // is closed showError.stackDepth = ++showError.stackDepth || 1; var popup = Qt.createComponent(Qt.resolvedUrl("ErrorDialog.qml")).createObject(caller); popup.errorMessage = errorMessage; if (showError.stackDepth > 1) { popup.buttonLabel = i18n.tr("Continue") } else { popup.buttonLabel = finalLabel || i18n.tr("Quit") popup.done.connect(function() { continuation(); }); } Popups.PopupUtils.open(popup.dialog); } checkbox-converged-1.2.4/components/ResultsPage.qml0000664000175000017500000010056312643477065022375 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Sylvain Pineau * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page for test results This page displays a pie charts and test results stats See design document at: http://goo.gl/6Igmhn */ import QtQuick 2.0 import Ubuntu.Components 1.1 import QtQuick.Layouts 1.1 Page { title: i18n.tr("Test Results") visible: false objectName: "resultsPage" property var results: {"totalPassed": 0, "totalFailed": 0, "totalSkipped": 0} property string submissionName: "" property var rerunEnabled: false signal saveReportClicked() signal submitReportClicked() signal endTesting() signal rerunTests() function unlatchSubmission() { submitResultsButton.unlatch(); } head { actions: [ Action { id: rerunAction objectName: "rerunAction" iconName: "view-refresh" text: i18n.tr("Rerun") onTriggered: rerunTests(); visible: rerunEnabled }, Action { iconName: "close" text: i18n.tr("Close") onTriggered: endTesting(); } ] } onResultsChanged: { chart_pie.chartData = [ { value: results.totalPassed, color:"#6AA84F", }, { value: results.totalFailed, color: "#DC3912", }, { value: results.totalSkipped, color: "#FF9900", }]; chart_pie.repaint(); } ColumnLayout { spacing: units.gu(2) anchors { fill: parent topMargin: units.gu(3) bottomMargin: units.gu(3) leftMargin: units.gu(1) rightMargin: units.gu(1) } Label { fontSize: "x-large" text: i18n.tr("Summary") } MouseArea { Layout.fillHeight: true Layout.fillWidth: true property var easter: 0 PieChart { id: chart_pie; anchors.fill: parent } Image { id: img visible: false fillMode: Image.Stretch anchors.fill: parent source: "data:image/png;base64,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" } onDoubleClicked: { easter = easter + 1 if (easter === 4) { chart_pie.visible = false chart_pie.height = 0 img.visible = true } } } Column { id: legend Row { spacing: units.gu(1) Text { text: "█" color:"#6AA84F" } Text { objectName: "passedLabel" text: results.totalPassed + " " + i18n.tr("tests passed") } } Row { spacing: units.gu(1) Text { text: "█" color:"#DC3912" } Text { objectName: "failedLabel" text: results.totalFailed + " " + i18n.tr("tests failed") } } Row { spacing: units.gu(1) Text { text: "█" color:"#FF9900" } Text { objectName: "skippedLabel" text: results.totalSkipped + " " + i18n.tr("tests skipped") } } } LatchButton { id: saveResultsButton unlatchedColor: UbuntuColors.green Layout.fillWidth: true text: i18n.tr("Save detailed report") onLatchedClicked: { rerunAction.enabled = false; saveReportClicked(); } } LatchButton { id: submitResultsButton unlatchedColor: UbuntuColors.green visible: submissionName Layout.fillWidth: true // TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" text: i18n.tr("Submit results to " + submissionName) onLatchedClicked: { rerunAction.enabled = false; submitReportClicked(); } } } } checkbox-converged-1.2.4/components/QmlConfinedPage.qml0000664000175000017500000001113012643477065023122 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page for Qml native test */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import QtQuick.Layouts 1.1 import Ubuntu.Content 1.1 import "actions" Page { id: qmlNativePage property var test: { "name": "", "description": "", "test_number": 0, "tests_count": 0} property var applicationVersion: "" signal testDone(var test); objectName: "qmlNativePage" title: i18n.tr("Test Description") /* testingShell serves as the interface to the external world from the * qml-test. */ Object { id: testingShell property string name: "Checkbox-touch qml confined shell" property alias pageStack: qmlNativePage.pageStack property string sessionDir: app.sessionDir property var python: app.py function getTest() { return test; } } Connections { target: ContentHub onImportRequested: { var resultFile = String(transfer.items[0].url).replace( 'file://', ''); var xhr = new XMLHttpRequest; xhr.open("GET", resultFile); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { try { var result = JSON.parse(xhr.responseText); test["outcome"] = result["outcome"]; testDone(test); } catch (x) { console.error("ERROR when parsing result json obj.") } } } xhr.send(); transfer.finalize(); } } head { actions: [ AddCommentAction {}, SkipAction {} ] } TestPageBody { header: test["name"] body: test["description"] ColumnLayout { id: busyIndicatorGroup visible: false Layout.fillWidth: true Layout.fillHeight: true Label { fontSize: "large" Layout.fillWidth: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere text: i18n.tr("Waiting for the test to finish") font.bold: true } } LatchButton { objectName: "continueButton" color: UbuntuColors.green Layout.fillWidth: true text: i18n.tr("Start the test") onClicked: { var appName = "com.ubuntu.checkbox_" + test["partial_id"]; // load the test qml file to check which permissions should be cleared var testItemComponent = Qt.createComponent(Qt.resolvedUrl(test['qml_file'])); if (testItemComponent.status == Component.Error) { console.error("Error creating testItemComponent. Possible cause: Problem with job's qml file. Error:", testItemComponent.errorString()); test['outcome'] = 'fail'; testDone(test); return; } var testItem = testItemComponent.createObject(dummyContainer, {"testingShell": testingShell}); var clearedPermissions = testItem.clearedPermissions; testItem.destroy(); var runConfinedTestApp = function() { busyIndicatorGroup.visible = true; Qt.openUrlExternally("application:///" + appName + "_" + applicationVersion+ ".desktop"); } if (clearedPermissions) { app.dropPermissions(appName, clearedPermissions, function() { runConfinedTestApp(); }); } else { runConfinedTestApp(); } } } Item { id: dummyContainer visible: false } } } checkbox-converged-1.2.4/components/ListItemWrappable.qml0000664000175000017500000000744412643477065023533 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.ListItems 1.0 /*! \brief ListItemWrappable \inherits Empty List item class similar to ListItems.Standard, but with wrappable text. It shows text and a control item. The height of the item will be automatically increased to fit the contained text label. */ Empty { id: listItem property alias control: controlContainer.control property var selected highlightWhenPressed: !listItem.control // Avoid emit clicked signals when clicking on the control area __acceptEvents: false height: (label.contentHeight < control.height ? control.height : label.contentHeight) + units.gu(2) __contents.anchors.topMargin: units.gu(1) __contents.anchors.bottomMargin: units.gu(1) __contentsMargins: 0 divider.anchors.leftMargin: 0 divider.anchors.rightMargin: 0 property bool __controlAreaPressed: false Rectangle { id: controlHighlight visible: listItem.swipingState === "" ? control && __controlAreaPressed : false anchors.fill: parent color: Theme.palette.selected.background } property bool __iconIsItem: false property alias __foregroundColor: label.color property real __rightIconMargin Label{ id: label property var selected: listItem.selected anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: __iconIsItem ? icon.width + 2 * listItem.__contentsMargins : listItem.__contentsMargins right: control ? controlContainer.left : (progression ? progressionHelper.left : parent.right) rightMargin: listItem.__contentsMargins } wrapMode: Text.WrapAtWordBoundaryOrAnywhere clip: true text: listItem.text verticalAlignment: Text.AlignVCenter } Item { id: controlContainer property Item control width: control ? control.width : undefined height: control ? control.height : undefined anchors { right: listItem.progression ? progressionHelper.left : parent.right rightMargin: listItem.__contentsMargins } onControlChanged: { if (control) control.parent = controlContainer; } Connections { target: listItem.__mouseArea onClicked: { if (control) { if (control.enabled && control.hasOwnProperty("clicked")) control.clicked(); } else { listItem.clicked(); } } onPressAndHold: { if (control && control.enabled && control.hasOwnProperty("pressAndHold")) { control.pressAndHold(); } else { listItem.pressAndHold(); } } } } onPressedChanged: { if (listItem.pressed && control && control.enabled) { listItem.__controlAreaPressed = true } else { listItem.__controlAreaPressed = false } } } checkbox-converged-1.2.4/components/CbtDialogLogic.js0000664000175000017500000000051312643477065022562 0ustar sylvainsylvain.import Ubuntu.Components.Popups 1.0 as Popups function showDialog(caller, message, buttons) { var p = Qt.createComponent(Qt.resolvedUrl("CbtDialog.qml")).createObject(caller); p.buttons = buttons || [{ "text": i18n.tr("OK"), "color": "UbuntuColors.green"}]; p.label = message; Popups.PopupUtils.open(p.dialog); } checkbox-converged-1.2.4/components/ResumeSessionPage.qml0000664000175000017500000000716012643477065023537 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Page for resume session options This page lets user resume session they were previously running See design document at: http://goo.gl/gpelpD */ import QtQuick 2.0 import Ubuntu.Components 1.1 import QtQuick.Layouts 1.1 Page { property alias resumeText: resumeLabel.text signal rerunLast(); signal continueSession(); signal restartSession(); objectName: "resumeSessionPage" title: i18n.tr("Resume session") visible: false ColumnLayout { id: columnLayout spacing: units.gu(3) anchors { fill: parent topMargin: units.gu(3) bottomMargin: units.gu(3) leftMargin: units.gu(1) rightMargin: units.gu(1) } function latchGroup() { rerunButton.state = "latched"; continueButton.state = "latched"; restartButton.state = "latched"; } Label { id: resumeLabel fontSize: "x-large" Layout.fillWidth: true Layout.fillHeight: true horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere } LatchButton { id: rerunButton objectName: "rerunButton" unlatchedColor: UbuntuColors.warmGrey Layout.fillWidth: true Label { objectName: "rerunButtonLabel" anchors.centerIn: parent Layout.fillHeight: true Layout.fillWidth: true color: "white" text: i18n.tr("Rerun last") } onLatchedClicked: { rerunLast(); columnLayout.latchGroup(); } } LatchButton { id: continueButton objectName: "continueButton" unlatchedColor: UbuntuColors.warmGrey Layout.fillWidth: true Label { objectName: "continueButtonLabel" anchors.centerIn: parent Layout.fillHeight: true Layout.fillWidth: true color: "white" text: i18n.tr("Continue") } onLatchedClicked: { continueSession(); columnLayout.latchGroup(); } } LatchButton { id: restartButton objectName: "restartButton" unlatchedColor: UbuntuColors.warmGrey Layout.fillWidth: true Label { objectName: "restartButtonLabel" anchors.centerIn: parent Layout.fillHeight: true Layout.fillWidth: true color: "white" text: i18n.tr("Start new session") } onLatchedClicked: { restartSession(); columnLayout.latchGroup(); } } } } checkbox-converged-1.2.4/components/PythonLogger.qml0000664000175000017500000000663012643477065022560 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2015 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /*! \brief Python-driven logger \inherits PythonObjectRef This component uses pyotherside to forward logging events to python. It monkey-patches console.log and console.error to capture their calls and forward those calls to python logger. */ import QtQuick 2.0 PythonObjectRef { id: pythonLogger function debug(msg) { invoke('debug', [msg], function() {}); } function info(msg) { invoke('info', [msg], function() {}); } function warning(msg) { invoke('warning', [msg], function() {}); } function error(msg) { invoke('error', [msg + "\nStacktrace:\n" + getStackTrace()], function() {}); } function critical(msg) { invoke('critical', [msg + "\nStacktrace:\n" + getStackTrace()], function() {}); } /** get string containing pretty-formatted stack trace */ function getStackTrace() { var stackTrace = ""; var callers = ((new Error).stack).split("\n"); callers.shift(); // remove current frame from ST (getStackTrace) callers.shift(); // remove logging method frame from ST for (var lvl in callers) { stackTrace += "#" + lvl + " " + callers[lvl] + "\n"; } return stackTrace; } /** Overridden invoke that doesn't log invoke calls */ function invoke(func, args, callback) { if (py !== null && object !== null) { var callable = py.getattr(object, func); if (!callable) { console.error("Unable to invoke " + func + " on python logger"); throw "trying to invoke not existing method" } py.call(callable, args, function(response) { callback(response); }); } else { _original_console_error("unable to invoke " + func + " on python logger"); throw "invoke called without py initiated and/or object constructed"; } } Component.onCompleted: { /* save original logging facilities */ _original_console_log = console.log; _original_console_error = console.error; } onObjectReady: { /* monkey-patch console.log and console.error */ console.log = function() { info(_argsToString(arguments)); }; console.error = function() { error(_argsToString(arguments)); }; debug("Python logger ready"); } /** get string containing unpacked values from arguments object separated by spaces*/ function _argsToString(argsObj) { var args = []; for(var i = 0; i < argsObj.length; i++) args.push(argsObj[i]); return args.join(" "); } property var _original_console_log property var _original_console_error } checkbox-converged-1.2.4/checkbox-touch.json0000664000175000017500000000012512643477065021031 0ustar sylvainsylvain{ "policy_groups": [], "policy_version": 1.2, "template": "unconfined" } checkbox-converged-1.2.4/py/0000775000175000017500000000000012645256742015661 5ustar sylvainsylvaincheckbox-converged-1.2.4/py/sudo_with_pass_ctrl.py0000664000175000017500000001723312643477065022321 0ustar sylvainsylvain# This file is part of Checkbox. # # Copyright 2015 Canonical Ltd. # Written by: # Maciej Kisielewski # # Checkbox is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, # as published by the Free Software Foundation. # # Checkbox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Checkbox. If not, see . """ :mod:`sudo_with_pass_ctrl` -- Self Providing Sudo Execution Controller ====================================================================== This module defines an Execution Controller capable of running plainbox' jobs as the user supplied in job definition. It does so by launching `sudo -S (...)` in a subprocess and writing password to the subprocess' stdin. """ try: import grp except ImportError: grp = None import logging import os try: import posix except ImportError: posix = None import subprocess from plainbox.i18n import gettext as _ from plainbox.impl.ctrl import CheckBoxDifferentialExecutionController logger = logging.getLogger("checkbox_touch.sudo_with_pass_ctrl") class RootViaSudoWithPassExecutionController( CheckBoxDifferentialExecutionController): """ Execution controller that gains root by using sudo, automatically supplying password to the sudo process. This controller should be used for jobs that need root in front-ends that are not CLI based. """ def __init__(self, provider_list, password_provider_cls): """ Initialize a new RootViaSudoWithPassExecutionController :param provider_list: A list of Provider1 objects that will be available for script dependency resolutions. Currently all of the scripts are makedirs available but this will be refined to the minimal set later. :param password_provider_cls: A callable that will be used to obtain user's password. It is called when the controller runs a sudo command. """ super().__init__(provider_list) try: in_sudo_group = grp.getgrnam("sudo").gr_gid in posix.getgroups() except KeyError: in_sudo_group = False try: in_admin_group = grp.getgrnam("admin").gr_gid in posix.getgroups() except KeyError: in_admin_group = False self.user_can_sudo = in_sudo_group or in_admin_group self._password_provider = password_provider_cls def get_execution_command(self, job, job_state, config, session_dir, nest_dir): """ Get the command to execute the specified job :param job: job definition with the command and environment definitions :param job_state: The JobState associated to the job to execute. :param config: A PlainBoxConfig instance which can be used to load missing environment definitions that apply to all jobs. It is used to provide values for missing environment variables that are required by the job (as expressed by the environ key in the job definition file). :param session_dir: Base directory of the session this job will execute in. This directory is used to co-locate some data that is unique to this execution as well as data that is shared by all executions. :param nest_dir: A directory with a nest of symlinks to all executables required to execute the specified job. This argument may or may not be used, depending on how PATH is passed to the command (via environment or via the commant line) :returns: List of command arguments """ # Run env(1) as the required user # --prompt is used to set sudo promp to an empty string so we don't # pollute command output # --reset-timestamp makes sudo ask for password even if it has been # supplied recently cmd = ['sudo', '--prompt=', '--reset-timestamp', '--stdin', '--user', job.user, 'env'] # Append all environment data env = self.get_differential_execution_environment( job, job_state, config, session_dir, nest_dir) cmd += ["{key}={value}".format(key=key, value=value) for key, value in sorted(env.items())] # Lastly use job.shell -c, to run our command cmd += [job.shell, '-c', job.command] return cmd def execute_job(self, job, job_state, config, session_dir, extcmd_popen): """ Execute the job using standard subprocess.Popen :param job: The JobDefinition to execute :param job_state: The JobState associated to the job to execute. :param config: A PlainBoxConfig instance which can be used to load missing environment definitions that apply to all jobs. It is used to provide values for missing environment variables that are required by the job (as expressed by the environ key in the job definition file). :param session_dir: Base directory of the session this job will execute in. This directory is used to co-locate some data that is unique to this execution as well as data that is shared by all executions. :param extcmd_popen: A subprocess.Popen like object - ignored :returns: The return code of the command, as returned by subprocess.call() The reason behind not using extcmd_popen is that it doesn't support connecting pipe to stdin of the process it spawns. And this is required for running 'sudo -S'. """ if not os.path.isdir(self.get_CHECKBOX_DATA(session_dir)): os.makedirs(self.get_CHECKBOX_DATA(session_dir)) # Setup the executable nest directory with self.configured_filesystem(job, config) as nest_dir: # Get the command and the environment. # of this execution controller cmd = self.get_execution_command( job, job_state, config, session_dir, nest_dir) env = self.get_execution_environment( job, job_state, config, session_dir, nest_dir) with self.temporary_cwd(job, config) as cwd_dir: # run the command logger.debug(_("job[%s] executing %r with env %r in cwd %r"), job.id, cmd, env, cwd_dir) p = subprocess.Popen(cmd, stdin=subprocess.PIPE, env=env, cwd=cwd_dir) # sudo manpage explicitly states that \n should be appended pass_bytes = bytes(self._password_provider() + '\n', 'UTF-8') p.communicate(input=pass_bytes) return_code = p.returncode if 'noreturn' in job.get_flag_set(): self._halt() return return_code def get_checkbox_score(self, job): """ Compute how applicable this controller is for the specified job. :returns: -1 if the job does not have a user override or the user cannot use sudo and 2 otherwise """ # Only makes sense with jobs that need to run as another user if job.user is not None and self.user_can_sudo: return 2 else: return -1 checkbox-converged-1.2.4/py/embedded_providers.py0000664000175000017500000001047212643477065022066 0ustar sylvainsylvain# This file is part of Checkbox. # # Copyright 2014 Canonical Ltd. # Written by: # Zygmunt Krynicki # Maciej Kisielewski # # Checkbox is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, # as published by the Free Software Foundation. # # Checkbox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Checkbox. If not, see . """ :mod:`embedded_providers` -- Checkbox-touch-only providers =============================================================== This module contains custom providers used by Checkbox-touch """ # NOTE: this is python 3.3 only from importlib.machinery import SourceFileLoader import os from plainbox.impl.secure.config import Unset from plainbox.impl.secure.plugins import FsPlugInCollection from plainbox.impl.secure.plugins import now from plainbox.impl.secure.providers.v1 import Provider1 from plainbox.impl.secure.providers.v1 import Provider1Definition from plainbox.impl.secure.providers.v1 import Provider1PlugIn class ManagePyProvider1PlugIn(Provider1PlugIn): """ Provider1PlugIn that is built from manage.py file. """ def __init__(self, filename, file_contents, load_time, *, validate=None, validation_kwargs=None, check=None, context=None): """ Initialize plug-in and create provider from definition extracted from manage.py pointed by `filename` """ self._load_time = load_time # override provider_manager.setup() to capture setup's parameters setup_kwargs = [] start_time = now() def fake_setup(**kwargs): setup_kwargs.append(kwargs) from plainbox import provider_manager provider_manager.setup = fake_setup loader = SourceFileLoader('manage', filename) loader.load_module() location = os.path.dirname(os.path.abspath(filename)) if len(setup_kwargs) < 1: # did not load any provider from given manage.py # creating empty definition definition = Provider1Definition() else: setup_kwargs = setup_kwargs.pop() definition = Provider1Definition() definition.location = location definition.name = setup_kwargs.get('name', None) definition.version = setup_kwargs.get('version', None) definition.description = setup_kwargs.get('description', None) definition.gettext_domain = setup_kwargs.get( 'gettext_domain', Unset) self._provider = Provider1.from_definition( definition, secure=False, validate=validate, validation_kwargs=validation_kwargs, check=check, context=context) self._wrap_time = now() - start_time @property def plugin_name(self): """ plugin name, the namespace of the provider """ return self._provider.name @property def plugin_object(self): """ plugin object, the actual Provider1 instance """ return self._provider @property def plugin_load_time(self) -> float: """ time, in fractional seconds, that was needed to load the provider definition file from the file system """ return self._load_time @property def plugin_wrap_time(self) -> float: """ time, in fractional seconds, that was needed to load the provider from the definition text """ return self._wrap_time class EmbeddedProvider1PlugInCollection(FsPlugInCollection): """ A collection of v1 provider plugins loaded from 'manage.py' files. """ def __init__(self, path, **kwargs): """ Initiates collection with all providers loaded from manage.py files found in `path` and subdirectories. """ # get all manage.py files to load providers from super().__init__( [path], 'manage.py', recursive=True, load=True, wrapper=ManagePyProvider1PlugIn, **kwargs) checkbox-converged-1.2.4/py/checkbox_touch.py0000664000175000017500000005223712645256742021234 0ustar sylvainsylvain# This file is part of Checkbox. # # Copyright 2014-2015 Canonical Ltd. # Written by: # Zygmunt Krynicki # Maciej Kisielewski # # Checkbox is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, # as published by the Free Software Foundation. # # Checkbox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Checkbox. If not, see . """ :mod:`checkbox_stack` -- Python Interface to Checkbox QML Stack =============================================================== This module is a part of the implementation of the Checkbox QML Stack. It is being imported at startup of all QML applications that are using Checkbox. """ import sys # Sanitize sys.path so that it doesn't cause issues while loading on a # sandboxed Ubuntu environment. sys.path = [item for item in sys.path if not item.startswith('/usr/local')] import abc import collections import datetime import json import logging import os import pyotherside import sqlite3 import re import time import traceback from plainbox.abc import IJobResult from plainbox.impl import pod from plainbox.impl.clitools import ToolBase from plainbox.impl.commands.inv_run import SilentUI from plainbox.impl.result import JobResultBuilder from plainbox.impl.session.assistant import SessionAssistant import plainbox from embedded_providers import EmbeddedProvider1PlugInCollection _logger = logging.getLogger('checkbox.touch') class CheckboxTouchUI(SilentUI): """ Class that connects checkbox-touch with plainbox. This class inherits SilentUI as most of the events happening in plainbox back-end are handled elsewhere. """ def got_program_output(self, stream_name, line): pyotherside.send("command_output", line) class PlainboxApplication(metaclass=abc.ABCMeta): """ Base class for plainbox-based applications. Concrete subclasses of this class should implement get_version_pair() and add any additional methods that they would like to call. """ def __repr__(self): return "<{}>".format(self.__class__.__name__) @abc.abstractmethod def get_version_pair(self): """ Get core (plainbox) and application version """ def view(func): """ Decorator for QML view functions """ def wrapper(*args, **kwargs): _logger.debug( "%s(%s)", func.__name__, ', '.join( [repr(arg) for arg in args] + ['{}={!r}'.format(ka, kv) for ka, kv in kwargs.items()])) try: result = func(*args, **kwargs) except Exception as exc: _logger.error("%s(...) raised %r", func.__name__, exc) traceback.print_exc() return { 'code': 500, 'error': str(exc), 'traceback': traceback.format_exc() } else: _logger.debug("%s(...) -> %r", func.__name__, result) return { 'code': 200, 'result': result } return wrapper def not_contains(a, b): """ The 'b not in a' operator, notably missing from the operator module """ return b not in a class CheckboxTouchApplication(PlainboxApplication): """ Class implementing the whole checkbox-touch application logic. This class exposes methods that can be called by the javascript embedded into many of the QML views. Each method implements a request / response semantics where the request is the set of data passed to python from javascript and the response is the python dictionary returned and processed back on the javascript side. This model follows the similar web development mechanics where the browser can issue asynchronous requests in reaction to user interactions and uses response data to alter the user interface. """ __version__ = (1, 2, 4, '', '') def __init__(self): if plainbox.__version__ < (0, 22): raise SystemExit("plainbox 0.22 required, you have {}".format( ToolBase.format_version_tuple(plainbox.__version__))) self.assistant = SessionAssistant('checkbox-converged') self.ui = CheckboxTouchUI() self.index = 0 self._password = None self._timestamp = None self._latest_session = None self.test_plan_id = None self.resume_candidate_storage = None self.assistant.use_alternate_repository( self._get_app_cache_directory()) # Prepare custom execution controller list from plainbox.impl.ctrl import UserJobExecutionController from sudo_with_pass_ctrl import RootViaSudoWithPassExecutionController ctrl_setup_list = [(RootViaSudoWithPassExecutionController, [self._password_provider], {}), (UserJobExecutionController, [], {}), ] self.assistant.use_alternate_execution_controllers(ctrl_setup_list) def __repr__(self): return "app" @view def load_providers(self, providers_dir): self.assistant.select_providers( '*', additional_providers=self._get_embedded_providers(providers_dir)) @view def get_version_pair(self): return { 'plainbox_version': ToolBase.format_version_tuple( plainbox.__version__), 'application_version': ToolBase.format_version_tuple( self.__version__) } @view def start_session(self): """Start a new session.""" self.assistant.start_new_session('Checkbox Converged session') self._timestamp = datetime.datetime.utcnow().isoformat() return { 'session_id': self.assistant.get_session_id(), 'session_dir': self.assistant.get_session_dir() } @view def resume_session(self, rerun_last_test): """ Resume latest sesssion. :param rerun_last_test: A bool stating whether runtime should repeat the test, that the app was executing when it was interrupted. """ metadata = self.assistant.resume_session(self._latest_session) app_blob = json.loads(metadata.app_blob.decode("UTF-8")) self.index = app_blob['index_in_run_list'] self.test_plan_id = app_blob['test_plan_id'] self.assistant.select_test_plan(self.test_plan_id) self.assistant.bootstrap() if not rerun_last_test: # Skip current test test = self.get_next_test()['result'] test['outcome'] = 'skip' self.register_test_result(test) return { 'session_id': self._latest_session } @view def clear_session(self): """Reset app-custom state info about the session.""" self.index = 0 self._timestamp = datetime.datetime.utcnow().isoformat() self._finalize_session() @view def is_session_resumable(self): """Check whether there is a session that can be resumed.""" for session_id, session_md in self.assistant.get_resumable_sessions(): if session_md.app_blob is None: continue # we're interested in the latest session only, this is why we # return early self._latest_session = session_id return { 'resumable': True, 'error_encountered': False, } else: return { 'resumable': False, 'error_encountered': False, } @view def get_testplans(self): """Get the list of available test plans.""" test_plan_units = [self.assistant.get_test_plan(tp_id) for tp_id in self.assistant.get_test_plans()] return { 'testplan_info_list': [{ "mod_id": tp.id, "mod_name": tp.name, "mod_selected": False, } for tp in test_plan_units] } @view def remember_testplan(self, test_plan_id): """Pick the test plan as the one in force.""" if self.test_plan_id: # test plan has been previously selected. User changed mind, we # have to abandon the session self._finalize_session() self.assistant.start_new_session('Checkbox Converged session') self._timestamp = datetime.datetime.utcnow().isoformat() self.test_plan_id = test_plan_id self.assistant.select_test_plan(test_plan_id) self.assistant.bootstrap() # because session id (and storage) might have changed, let's share this # info with the qml side return { 'session_id': self.assistant.get_session_id(), 'session_dir': self.assistant.get_session_dir() } @view def get_categories(self): """Get categories selection data.""" category_info_list = [{ "mod_id": category.id, "mod_name": category.name, "mod_selected": True, } for category in ( self.assistant.get_category(category_id) for category_id in self.assistant.get_participating_categories() )] return {'category_info_list': category_info_list} @view def remember_categories(self, selected_id_list): """Save category selection.""" _logger.info("Selected categories: %s", selected_id_list) # Remove previously set filters self.assistant.remove_all_filters() self.assistant.filter_jobs_by_categories(selected_id_list) @view def get_available_tests(self): """ Get all tests for selection purposes. The response object will contain only tests with category matching previously set list. Tests are sorted by (category, name) """ category_names = { cat_id: self.assistant.get_category(cat_id).tr_name() for cat_id in self.assistant.get_participating_categories()} job_units = [self.assistant.get_job(job_id) for job_id in self.assistant.get_static_todo_list()] test_info_list = [{ "mod_id": job.id, "mod_name": job.tr_summary(), "mod_group": category_names[job.category_id], "mod_selected": True, } for job in job_units] test_info_list.sort(key=lambda ti: (ti['mod_group'], ti['mod_name'])) return {'test_info_list': test_info_list} @view def get_rerun_candidates(self): """Get all the tests that might be selected for rerunning.""" def rerun_predicate(job_state): return job_state.result.outcome in ( IJobResult.OUTCOME_FAIL, IJobResult.OUTCOME_CRASH, IJobResult.OUTCOME_NOT_SUPPORTED, IJobResult.OUTCOME_SKIP) rerun_candidates = [] todo_list = self.assistant.get_static_todo_list() job_units = {job_id: self.assistant.get_job(job_id) for job_id in todo_list} job_states = {job_id: self.assistant.get_job_state(job_id) for job_id in todo_list} category_names = { cat_id: self.assistant.get_category(cat_id).tr_name() for cat_id in self.assistant.get_participating_categories()} for job_id, job_state in job_states.items(): if rerun_predicate(job_state): rerun_candidates.append({ "mod_id": job_id, "mod_name": job_units[job_id].tr_summary(), "mod_group": category_names[job_units[job_id].category_id], "mod_selected": False }) return rerun_candidates @view def remember_tests(self, selected_id_list): """Save test selection.""" self.index = 0 self.assistant.use_alternate_selection(selected_id_list) self.assistant.update_app_blob(self._get_app_blob()) _logger.info("Selected tests: %s", selected_id_list) return @view def get_next_test(self): """ Get next text that is scheduled to run. :returns: Dictionary resembling JobDefinition or None if all tests are completed """ todo_list = self.assistant.get_static_todo_list() if self.index < len(todo_list): job = self.assistant.get_job(todo_list[self.index]) description = "" if job.tr_purpose() is not None: description = job.tr_purpose() + "\n" if job.tr_steps() is not None: description += job.tr_steps() if not description: description = job.tr_description() test = { "name": job.tr_summary(), "description": description, "verificationDescription": job.tr_verification() if job.tr_verification() is not None else description, "plugin": job.plugin, "id": job.id, "partial_id": job.partial_id, "user": job.user, "qml_file": job.qml_file, "start_time": time.time(), "test_number": todo_list.index(job.id), "tests_count": len(todo_list), "command": job.command, "flags": job.get_flag_set() } return test else: return {} @view def register_test_result(self, test): """Registers outcome of a test.""" _logger.info("Storing test result: %s", test) job_id = test['id'] builder_kwargs = { 'outcome': test['outcome'], 'comments': test.get('comments', pod.UNSET), 'execution_duration': time.time() - test['start_time'] } try: # if we're registering skipped test as an outcome of resuming # session, the result field of the test object will be missing builder_kwargs['io_log_filename'] = test['result'].io_log_filename except KeyError: pass result = JobResultBuilder(**builder_kwargs).get_result() self.assistant.use_job_result(job_id, result) self.index += 1 self.assistant.update_app_blob(self._get_app_blob()) @view def run_test_activity(self, test): """Run command associated with given test.""" plugins_handled_natively = ['qml'] res_builder = self.assistant.run_job( test['id'], self.ui, test['plugin'] in plugins_handled_natively) test['outcome'] = res_builder.outcome test['result'] = res_builder return test @view def get_results(self): """Get results object.""" stats = self.assistant.get_summary() return { 'totalPassed': stats[IJobResult.OUTCOME_PASS], 'totalFailed': stats[IJobResult.OUTCOME_FAIL], 'totalSkipped': stats[IJobResult.OUTCOME_SKIP] + stats[IJobResult.OUTCOME_NOT_SUPPORTED] } @view def export_results(self, output_format, option_list): """Export results to file(s) in the user's 'Documents' directory..""" self.assistant.finalize_session() dirname = self._get_user_directory_documents() return self.assistant.export_to_file( output_format, option_list, dirname) @view def submit_results(self, config): """Submit results to a service configured by config.""" self.assistant.finalize_session() transport = { 'hexr': self.assistant.get_canonical_hexr_transport, 'hexr-staging': ( lambda: self.assistant.get_canonical_hexr_transport( staging=True)), 'c3': ( lambda: self.assistant.get_canonical_certification_transport( config['secure_id'])), 'c3-staging': ( lambda: self.assistant.get_canonical_certification_transport( config['secure_id'], staging=True)), }[config['type']]() # CertificationTransport expects xml submission format, 'hexr' exporter # provides compliant one return self.assistant.export_to_transport( '2013.com.canonical.plainbox::hexr', transport) @view def drop_permissions(self, app_id, services): # TODO: use XDG once available trust_dbs = { 'camera': '~/.local/share/CameraService/trust.db', 'audio': '~/.local/share/PulseAudio/trust.db', 'location': '~/.local/share/UbuntuLocationServices/trust.db', } sql = 'delete from requests where ApplicationId = (?);' for service in services: conn = None try: conn = sqlite3.connect( os.path.expanduser(trust_dbs[service]), isolation_level='EXCLUSIVE') conn.execute(sql, (app_id,)) conn.commit() finally: if conn: conn.close() def _get_user_directory_documents(self): xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or \ os.path.expanduser('~/.config') with open(os.path.join(xdg_config_home, 'user-dirs.dirs')) as f: match = re.search(r'XDG_DOCUMENTS_DIR="(.*)"\n', f.read()) if match: return match.group(1).replace("$HOME", os.getenv("HOME")) else: return os.path.expanduser('~/Documents') def _get_app_cache_directory(self): xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \ os.path.expanduser('~/.cache') app_id = os.environ.get('APP_ID') if app_id: # Applications will always have write access to directories they # own as determined by the XDG base directory specification. # Specifically: XDG_CACHE_HOME/ # XDG_RUNTIME_DIR/ # XDG_RUNTIME_DIR/confined/ (for TMPDIR) # XDG_DATA_HOME/ # XDG_CONFIG_HOME/ # Note that is not the APP_ID. In order to easily # handle upgrades and sharing data between executables in the same # app, we use the unversioned app package name for the writable # directories. return os.path.join(xdg_cache_home, app_id.split('_')[0]) else: path = os.path.join(xdg_cache_home, "com.ubuntu.checkbox") if not os.path.exists(path): os.makedirs(path) elif not os.path.isdir(path): # as unlikely as it is, situation where path exists and is a # regular file neeeds to be signalled raise IOError("{} exists and is not a directory".format(path)) return path def _get_app_blob(self): """ Get json dump of with app-specific blob """ return json.dumps( { 'version': 1, 'test_plan_id': self.test_plan_id, 'index_in_run_list': self.index, 'session_timestamp': self._timestamp, }).encode("UTF-8") def _get_embedded_providers(self, providers_dir): """ Get providers included with the app :param providers_dir: Path within application tree from which to load providers :returns: list of loaded providers """ provider_list = [] app_root_dir = os.path.normpath(os.getenv( 'APP_DIR', os.path.join(os.path.dirname(__file__), '..'))) path = os.path.join(app_root_dir, os.path.normpath(providers_dir)) _logger.info("Loading all providers from %s", path) if os.path.exists(path): embedded_providers = EmbeddedProvider1PlugInCollection(path) provider_list += embedded_providers.get_all_plugin_objects() return provider_list def _password_provider(self): if self._password is None: raise RuntimeError("execute_job called without providing password" " first") return self._password def _finalize_session(self): self.test_plan_id = "" self.assistant.finalize_session() def remember_password(self, password): """ Save password in app instance It deliberately doesn't use view decorator to omit all logging that might happen """ self._password = password def get_qml_logger(default_level): logging_level = collections.defaultdict(lambda: logging.INFO, { "debug": logging.DEBUG, "warning": logging.WARNING, "warn": logging.WARN, "error": logging.ERROR, "critical": logging.CRITICAL, "fatal": logging.FATAL})[default_level.lower()] logging.basicConfig(level=logging_level, stream=sys.stderr) return logging.getLogger('checkbox.touch.qml') create_app_object = CheckboxTouchApplication checkbox-converged-1.2.4/checkbox-touch.qml0000664000175000017500000006437112645002025020644 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014, 2015 Canonical Ltd. * * Authors: * - Zygmunt Krynicki * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.Popups 0.1 import QtQuick.Layouts 1.1 import io.thp.pyotherside 1.4 import "components" import "components/ErrorLogic.js" as ErrorLogic import "components/CbtDialogLogic.js" as CbtDialogLogic /*! \brief MainView with a Label and Button elements. */ MainView { id: mainView // objectName for functional testing purposes (autopilot-qt5) objectName: "mainView" // Note! applicationName needs to match the "name" field of the click manifest applicationName: "com.canonical.certification.checkbox-touch" /* This property enables the application to change orientation when the device is rotated. The default is false. */ //automaticOrientation: true width: units.gu(100) height: units.gu(75) useDeprecatedToolbar: false // appSettings serves as application-wide storage for global variables // it has to have at least one entry to be constructed property var appSettings: { "applicationName" : applicationName, "revision": "unknown revision", "testplan": "", "providersDir": "providers", "submission": null } Arguments { id: args Argument { name: "autopilot" help: i18n.tr("Run Checkbox-Touch in autopilot-testing mode") required: false } Argument { name: "quiet" help: i18n.tr("Write only warnings and errors to standard error") required: false } Argument { name: "settings" valueNames: "PATH_TO_SETTINGS" help: i18n.tr("Path to a file containing checkbox-touch settings") required: true } } Component.onCompleted: { i18n.domain = "com.ubuntu.checkbox"; if (args.values["autopilot"]) { // autopilot-testing mode appSettings["testplan"] = "2015.com.canonical.certification::checkbox-touch-autopilot"; appSettings["providersDir"] = "tests/autopilot/autopilot-provider"; appSettings["log-level"] = "warning"; } else { // normal execution - load settings.json file var xhr = new XMLHttpRequest; xhr.open("GET", args.values["settings"]); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { try { var newAppSettings = JSON.parse(xhr.responseText); } catch (x) { // if we cannot parse settings.json, we should leave // deafult values of appSettings console.error("Could not parse settings.json. Using default values"); } // overwrite/add appSettings' attributes that got loaded for (var attr in newAppSettings) { appSettings[attr] = newAppSettings[attr]; } } } xhr.send(); } if (args.values["quiet"]) { // monkey-patch console.log and console.info to do nothing console.log = function() {}; console.info = function() {}; appSettings["log-level"] = "warning"; } py.init() } // Pyotherside python object that we use to talk to all of plainbox Python { id: py function init() { console.log("Pyotherside version " + pluginVersion()); console.log("Python version " + pythonVersion()); // A bit hacky but that's where the python code is addImportPath(Qt.resolvedUrl('py/')); // Import path for plainbox and potentially other python libraries addImportPath(Qt.resolvedUrl('lib/py')) setHandler('command_output', commandOutputPage.addText); initiated(); } // gets triggered when python object is ready to be used signal initiated onError: { console.error("python error: " + traceback); ErrorLogic.showError(mainView, "python error: " + traceback, Qt.quit); } onReceived: console.log("pyotherside.send: " + data) } // Component representing our application CheckboxTouchApplication { id: app py: py onAppReady: { console.log("Plainbox version " + plainboxVersion); console.log("Checkbox Touch version " + applicationVersion); aboutPage.versionInfo = { "checkbox_touch" : applicationVersion, "plainbox" : plainboxVersion }; resumeOrStartSession(); } onSessionReady: { welcomePage.enableButton() } Component.onCompleted: { // register to py.initiated signal py.onInitiated.connect(function() { construct("checkbox_touch.create_app_object", []); }); } } PythonLogger { id: logger py: py Component.onCompleted: { // register to py.initiated signal py.onInitiated.connect(function() { py.importModule("checkbox_touch", function() { construct("checkbox_touch.get_qml_logger", [appSettings["log-level"] || "info"]); }); }); } } PageStack { id: pageStack Component.onCompleted: push(welcomePage) } WelcomePage { id: welcomePage // TRANSLATORS: %1 means program version, %2 repository revision and %3 // date when the package was built // TRANSLATORS: keep the '\n' characters at the end of each line welcomeText: i18n.tr("Welcome to Checkbox Touch\nVersion: %1\n(%2 %3)") .arg(app.applicationVersion).arg(appSettings.revision).arg(appSettings.clickBuildDate) onStartTestingTriggered: { if (appSettings.testplan != "") { app.rememberTestplan(appSettings.testplan, function() { categorySelectionPage.setup(); enableButton(); }); } else { app.getTestplans(function(response) { var tp_list = response.testplan_info_list; if (tp_list.length < 2 && tp_list.length > 0) { app.rememberTestplan(tp_list[0].mod_id, function() { categorySelectionPage.setup(); }); } else { testplanSelectionPage.setup(tp_list) } enableButton(); }); } } onAboutClicked: pageStack.push(aboutPage) } AboutPage { id: aboutPage visible: false } Item { id: progressHeader visible: false property alias progressText: _progressText.text property alias value: _progressBar.value property alias maximumValue: _progressBar.maximumValue ColumnLayout{ anchors { fill: parent verticalCenter: parent.verticalCenter bottomMargin: units.gu(1.5) rightMargin: units.gu(1) // leftMargin should compensate for potential 'back' action that might appear on next page // so the whole progressHeader stays in the same spot on the screen throughout the session leftMargin: pageStack.depth == 1 ? units.gu(5) : units.gu(1) } Label { text: pageStack.currentPage ? pageStack.currentPage.title : "" fontSize: "x-large" font.weight: Font.Light anchors.verticalCenter: parent.verticalCenter } Label { id: _progressText fontSize: "x-small" font.weight: Font.Light anchors.right: parent.right anchors.bottom: parent.bottom } ProgressBox { id: _progressBar value: 0 maximumValue: 1 implicitWidth: parent.width } } function update(test) { progressHeader.visible = true; progressHeader.progressText = Number(test.test_number / test.tests_count * 100.0).toFixed(0) + "% ("+test.test_number + "/" + test.tests_count + ")"; progressHeader.value = test.test_number progressHeader.maximumValue = test.tests_count } } ResumeSessionPage { id: resumeSessionPage onRerunLast: app.resumeSession(true, processNextTest) onContinueSession: app.resumeSession(false, processNextTest) resumeText: i18n.tr("Checkbox session got suspended.\nDo you want \ to rerun last test, continue to the next test, or start a new session?") onRestartSession: { pageStack.clear(); pageStack.push(welcomePage); app.startSession(); } } SelectionPage { id: testplanSelectionPage title: i18n.tr("Select test plan") onlyOneAllowed: true largeBuffer: args.values["autopilot"] function setup(testplan_info_list) { if (testplan_info_list.length<1) { ErrorLogic.showError(mainView, "Test plan missing", Qt.quit); } model.clear(); for (var i=0; i -1) performConfinedQmlTest(test); else performQmlTest(test); break; default: test.outcome = "skip"; completeTest(test); } } function completeTest(test) { app.registerTestResult(test, processNextTest); } function runTestActivity(test, continuation) { commandOutputPage.clear(); app.runTestActivity(test, continuation); } function showResultsScreen() { var endTesting = function() { pageStack.clear(); app.clearSession(function() { app.startSession(); pageStack.push(welcomePage); }); }; var saveReport = function() { app.exportResults('2013.com.canonical.plainbox::html', [], function(uri) { var htmlReportUrl = uri; app.exportResults('2013.com.canonical.plainbox::xlsx', ["with-sys-info", "with-summary", "with-job-description", "with-text-attachments", "with-unit-categories"], function(uri) { CbtDialogLogic.showDialog(mainView, i18n.tr("Reports have been saved to your Documents folder"), [{ "text": i18n.tr("OK"), "color": UbuntuColors.green}, {"text": i18n.tr("View Report"), "color": UbuntuColors.green, "onClicked": function(uri) { var webviewer = Qt.createComponent(Qt.resolvedUrl("components/WebViewer.qml")).createObject(); webviewer.uri = htmlReportUrl; pageStack.push(webviewer); }}]); }); }); }; var submitReport = function(resultsPage) { // resultsPage param is for having control over unlatching getSubmissionInput(function() { app.submitResults(appSettings["submission"], function(reply) { // pretty-stringify reply var s = "" for (var i in reply) { s += i + ': ' + reply[i] + '\n'; } CbtDialogLogic.showDialog( resultsPage, i18n.tr("Report has been submited.\n" + s), [{"text": i18n.tr("OK"), "color": UbuntuColors.green}]); }, function(error) { ErrorLogic.showError(mainView, i18n.tr("Could not submit results. Reason:\n" + error), function(){}, i18n.tr("OK")); resultsPage.unlatchSubmission(); }) }, function() { resultsPage.unlatchSubmission(); }); }; pageStack.clear(); app.getRerunCandidates(function(rerunCandidates) { app.getResults(function(results) { var resultsPage = createPage("components/ResultsPage.qml"); resultsPage.results = results; if (appSettings["submission"]) { resultsPage.submissionName = appSettings["submission"].name; } resultsPage.endTesting.connect(endTesting); resultsPage.saveReportClicked.connect(saveReport); resultsPage.submitReportClicked.connect(function() {submitReport(resultsPage);}); if (rerunCandidates.length>0) { resultsPage.rerunEnabled = true; resultsPage.rerunTests.connect(function() { rerunSelectionPage.setup(rerunCandidates); }); } pageStack.push(resultsPage); }); }); } function performAutomatedTest(test) { var automatedTestPage = createPage("components/AutomatedTestPage.qml", test); pageStack.push(automatedTestPage); runTestActivity(test, completeTest); } function performManualTest(test) { runTestActivity(test, function(test) { var manualIntroPage = createPage("components/ManualIntroPage.qml", test); manualIntroPage.testDone.connect(completeTest); manualIntroPage.continueClicked.connect(function() { showVerificationScreen(test); }); pageStack.push(manualIntroPage); }); } function performUserInteractVerifyTest(test) { var InteractIntroPage = createPage("components/InteractIntroPage.qml", test); InteractIntroPage.testStarted.connect(function() { runTestActivity(test, function(test) { InteractIntroPage.stopActivity(); showVerificationScreen(test); }); }); InteractIntroPage.testDone.connect(completeTest); pageStack.push(InteractIntroPage); } function performUserInteractTest(test) { var InteractIntroPage = createPage("components/InteractIntroPage.qml", test); InteractIntroPage.testStarted.connect(function() { runTestActivity(test, function(test) { InteractIntroPage.stopActivity(); var userInteractSummaryPage = createPage("components/UserInteractSummaryPage.qml", test); userInteractSummaryPage.testDone.connect(completeTest); pageStack.push(userInteractSummaryPage); }); }); InteractIntroPage.testDone.connect(completeTest); pageStack.push(InteractIntroPage); } function performUserVerifyTest(test) { var InteractIntroPage = createPage("components/InteractIntroPage.qml", test); InteractIntroPage.testDone.connect(completeTest); InteractIntroPage.testStarted.connect(function() { runTestActivity(test, function(test) { showVerificationScreen(test); } ); }); pageStack.push(InteractIntroPage); } function performQmlTest(test) { runTestActivity(test, function(test) { var qmlNativePage = createPage("components/QmlNativePage.qml", test); qmlNativePage.testDone.connect(completeTest); pageStack.push(qmlNativePage); }); } function performConfinedQmlTest(test) { runTestActivity(test, function(test) { var qmlNativePage = createPage("components/QmlConfinedPage.qml", test); qmlNativePage.applicationVersion = app.applicationVersion; qmlNativePage.testDone.connect(completeTest); pageStack.push(qmlNativePage); }); } function showVerificationScreen(test) { var verificationPage = createPage("components/TestVerificationPage.qml", test); var maybeCommentVerification = function(test) { if (test.outcome == 'fail' && test.flags.indexOf('explicit-fail') > -1) { commentsDialog.commentDefaultText = test["comments"] || ""; commentsDialog.commentAdded.connect(function(comment) { test["comments"] = comment; completeTest(test); }); PopupUtils.open(commentsDialog.dialogComponent); } else { completeTest(test); } } verificationPage.testDone.connect(maybeCommentVerification); pageStack.push(verificationPage); } function getSubmissionInput(continuation, cancelContinuation) { if (!appSettings.submission.input) { // no input to process continuation(); return; } var input_vars = appSettings.submission.input.slice(); //copy array // Because of the asynchronous nature of qml we cannot just launch // N number of popups each asking for one submission variable var process_input = function() { if (input_vars.length > 0) { var input = input_vars.shift(); var dlg = Qt.createComponent(Qt.resolvedUrl("components/InputDialog.qml")).createObject(mainView); dlg.prompt = input.prompt; dlg.textEntered.connect(function(text) { appSettings.submission[input.paramName] = text; process_input(); }); dlg.cancelClicked.connect(function() { cancelContinuation(); return; }); PopupUtils.open(dlg.dialogComponent); return; } continuation(); } process_input(); } } checkbox-converged-1.2.4/po/0000775000175000017500000000000012643477065015650 5ustar sylvainsylvaincheckbox-converged-1.2.4/po/ug.po0000664000175000017500000001707612643477065016636 0ustar sylvainsylvain# Uyghur translation for checkbox # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the checkbox package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: checkbox\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-10-15 12:23+0200\n" "PO-Revision-Date: 2015-10-28 14:31+0000\n" "Last-Translator: Eltikin \n" "Language-Team: Uyghur \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-11-28 04:35+0000\n" "X-Generator: Launchpad (build 17850)\n" #: checkbox-touch.qml:70 msgid "Run Checkbox-Touch in autopilot-testing mode" msgstr "" #: checkbox-touch.qml:75 msgid "Write only warnings and errors to standard error" msgstr "" #: checkbox-touch.qml:81 msgid "Path to a file containing checkbox-touch settings" msgstr "" #. TRANSLATORS: %1 means program version, %2 repository revision and %3 #. date when the package was built #. TRANSLATORS: keep the '\n' characters at the end of each line #: checkbox-touch.qml:198 #, qt-format msgid "" "Welcome to Checkbox Touch\n" "Version: %1\n" "(%2 %3)" msgstr "" #: checkbox-touch.qml:278 msgid "" "Previous session did not finish completely.\n" "Do you want to rerun last test, continue to the next test, or start a new " "session?" msgstr "" #: checkbox-touch.qml:289 msgid "Select test plan" msgstr "" #: checkbox-touch.qml:316 msgid "Select categories" msgstr "" #: checkbox-touch.qml:345 msgid "Select tests" msgstr "" #: checkbox-touch.qml:346 components/WelcomePage.qml:79 #: components/WelcomePage.qml:117 msgid "Start testing" msgstr "" #: checkbox-touch.qml:373 msgid "Select tests to re-run" msgstr "" #: checkbox-touch.qml:375 msgid "Finish" msgstr "تامام" #: checkbox-touch.qml:375 msgid "Re-run" msgstr "" #: checkbox-touch.qml:415 msgid "Could not create component '" msgstr "" #: checkbox-touch.qml:417 components/ErrorLogic.js:13 #: components/CheckboxTouchApplication.qml:57 msgid "Quit" msgstr "چېكىن" #: checkbox-touch.qml:436 msgid "Could not resume session." msgstr "" #: checkbox-touch.qml:438 components/ResumeSessionPage.qml:117 #: components/CheckboxTouchApplication.qml:70 msgid "Start new session" msgstr "" #: checkbox-touch.qml:547 msgid "Reports have been saved to your Documents folder" msgstr "" #: checkbox-touch.qml:548 checkbox-touch.qml:567 checkbox-touch.qml:573 #: components/CbtDialogLogic.js:5 components/PasswordDialog.qml:60 #: components/InputDialog.qml:58 msgid "OK" msgstr "تامام" #: checkbox-touch.qml:548 msgid "View report" msgstr "" #: checkbox-touch.qml:566 msgid "Report has been submited.\n" msgstr "" #: checkbox-touch.qml:571 msgid "Could not submit report. Reason:\n" msgstr "" #: confinement/plainbox-confined-shell.qml:97 msgid "Sending report - you should not see this page :-)" msgstr "" #: components/WelcomePage.qml:35 msgid "System Testing" msgstr "سىستېمىنى سىناش" #: components/WelcomePage.qml:47 components/AboutPage.qml:39 msgid "About" msgstr "ھەققىدە" #: components/WelcomePage.qml:73 msgid "Checkbox is loading..." msgstr "" #: components/QmlConfinedPage.qml:40 components/InteractIntroPage.qml:48 #: components/QmlNativePage.qml:38 components/ManualIntroPage.qml:42 msgid "Test Description" msgstr "" #: components/QmlConfinedPage.qml:98 msgid "Waiting for the test to finish" msgstr "" #: components/QmlConfinedPage.qml:107 components/InteractIntroPage.qml:99 #: components/QmlNativePage.qml:65 components/ManualIntroPage.qml:57 msgid "Start the test" msgstr "" #: components/ResumeSessionPage.qml:39 msgid "Resume session" msgstr "" #: components/ResumeSessionPage.qml:79 msgid "Rerun last" msgstr "" #: components/ResumeSessionPage.qml:98 components/SelectionPage.qml:36 #: components/ErrorLogic.js:11 components/UserInteractSummaryPage.qml:95 msgid "Continue" msgstr "داۋاملاشتۇر" #: components/TestVerificationPage.qml:41 #: components/UserInteractSummaryPage.qml:41 msgid "Verification" msgstr "ئىسپاتلاش" #. TRANSLATORS: this string is on a button that marks the given test as passed #: components/TestVerificationPage.qml:72 msgid "Pass" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as failed #: components/TestVerificationPage.qml:86 msgid "Fail" msgstr "" #: components/CommandOutputPage.qml:46 msgid "Command output" msgstr "بۇيرۇق چىقىرىلمىسى" #. TRANSLATORS: a verb (call to action) #: components/CommandOutputPage.qml:54 msgid "Copy" msgstr "كۆچۈرۈش" #. TRANSLATORS: This is a label on the button that goes back a page #: components/CommandOutputPage.qml:98 msgid "Back" msgstr "كەينى" #: components/PasswordDialog.qml:47 msgid "Enter password" msgstr "ئىم كىرگۈزۈڭ" #: components/PasswordDialog.qml:53 msgid "password" msgstr "ئىم" #: components/PasswordDialog.qml:72 components/InputDialog.qml:68 msgid "Cancel" msgstr "بولدىلا" #: components/SelectionPage.qml:89 msgid "Toggle selection" msgstr "" #: components/WebViewer.qml:28 msgid "Test Report" msgstr "" #: components/AutomatedTestPage.qml:39 msgid "Automated test" msgstr "" #. TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" #: components/UserInteractSummaryPage.qml:60 msgid "This test " msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "PASSED" msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "FAILED" msgstr "بۇزۇق" #: components/UserInteractSummaryPage.qml:88 msgid "You can go back to rerun the test or continue to the next test." msgstr "" #: components/actions/SkipAction.qml:33 msgid "Skip" msgstr "ئاتلا" #: components/actions/SkipAction.qml:36 msgid "Do you really want to skip this test?" msgstr "راستلا بۇ سىناقنى ئۆتكۈزۈۋاتامسىز؟" #: components/actions/AddCommentAction.qml:31 components/CommentsDialog.qml:54 msgid "Add comment" msgstr "" #: components/CommentsDialog.qml:66 msgid "Done" msgstr "تامام" #: components/ConfirmationDialog.qml:50 msgid "Are you sure?" msgstr "راستمۇ؟" #: components/ConfirmationDialog.qml:69 msgid "YES" msgstr "ھەئە" #: components/ConfirmationDialog.qml:78 msgid "NO" msgstr "ياق" #: components/ConfirmationDialog.qml:93 msgid "Do not ask me this question again" msgstr "" #: components/ResultsPage.qml:34 msgid "Test Results" msgstr "" #: components/ResultsPage.qml:52 components/AboutPage.qml:81 msgid "Close" msgstr "ياپ" #: components/ResultsPage.qml:87 msgid "Summary" msgstr "قىسقىچە مەزمۇنى" #: components/ResultsPage.qml:131 msgid "tests passed" msgstr "" #: components/ResultsPage.qml:142 msgid "tests failed" msgstr "" #: components/ResultsPage.qml:153 msgid "tests skipped" msgstr "" #: components/ResultsPage.qml:162 msgid "Save detailed report" msgstr "" #. TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" #: components/ResultsPage.qml:171 msgid "Submit results to " msgstr "" #: components/AboutPage.qml:74 msgid "version: " msgstr "" #: components/AboutPage.qml:75 msgid "Plainbox version: " msgstr "" #: components/ErrorDialog.qml:43 msgid "Um, OK" msgstr "" #: components/ErrorDialog.qml:53 components/ErrorDialog.qml:61 msgid "Error encountered" msgstr "" #: components/CheckboxTouchApplication.qml:55 msgid "Could not start a session. Reason:\n" msgstr "" #: components/CheckboxTouchApplication.qml:65 msgid "Could not resume session" msgstr "" #: py/sudo_with_pass_ctrl.py:160 #, python-format msgid "job[%s] executing %r with env %r in cwd %r" msgstr "" checkbox-converged-1.2.4/po/fr.po0000664000175000017500000002164612643477065016630 0ustar sylvainsylvain# French translation for checkbox # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the checkbox package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: checkbox\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-10-15 12:23+0200\n" "PO-Revision-Date: 2016-01-01 20:40+0000\n" "Last-Translator: Anne \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2016-01-02 05:07+0000\n" "X-Generator: Launchpad (build 17865)\n" #: checkbox-touch.qml:70 msgid "Run Checkbox-Touch in autopilot-testing mode" msgstr "Exécuter Checkbox-Touch en mode tests auto pilotés" #: checkbox-touch.qml:75 msgid "Write only warnings and errors to standard error" msgstr "" "Écrire uniquement les avertissements et les erreurs en erreurs standards" #: checkbox-touch.qml:81 msgid "Path to a file containing checkbox-touch settings" msgstr "Chemin vers un fichier contenant les paramètres de checkbox-touch" #. TRANSLATORS: %1 means program version, %2 repository revision and %3 #. date when the package was built #. TRANSLATORS: keep the '\n' characters at the end of each line #: checkbox-touch.qml:198 #, qt-format msgid "" "Welcome to Checkbox Touch\n" "Version: %1\n" "(%2 %3)" msgstr "" "Bienvenue dans Checkbox Touch\n" "Version : %1\n" "(%2 %3)" #: checkbox-touch.qml:278 msgid "" "Previous session did not finish completely.\n" "Do you want to rerun last test, continue to the next test, or start a new " "session?" msgstr "" "La session précédente ne s'est pas terminée complètement.\n" "Souhaitez-vous relancer le dernier test, continuer avec le test suivant ou " "démarrer une nouvelle session ?" #: checkbox-touch.qml:289 msgid "Select test plan" msgstr "Sélectionnez le plan de test" #: checkbox-touch.qml:316 msgid "Select categories" msgstr "Sélectionnez les catégories" #: checkbox-touch.qml:345 msgid "Select tests" msgstr "Sélectionnez les tests" #: checkbox-touch.qml:346 components/WelcomePage.qml:79 #: components/WelcomePage.qml:117 msgid "Start testing" msgstr "Démarrer le test" #: checkbox-touch.qml:373 msgid "Select tests to re-run" msgstr "Sélectionnez les tests à relancer" #: checkbox-touch.qml:375 msgid "Finish" msgstr "Terminer" #: checkbox-touch.qml:375 msgid "Re-run" msgstr "Relancer" #: checkbox-touch.qml:415 msgid "Could not create component '" msgstr "Impossible de créer le composant '" #: checkbox-touch.qml:417 components/ErrorLogic.js:13 #: components/CheckboxTouchApplication.qml:57 msgid "Quit" msgstr "Quitter" #: checkbox-touch.qml:436 msgid "Could not resume session." msgstr "Impossible de reprendre la session." #: checkbox-touch.qml:438 components/ResumeSessionPage.qml:117 #: components/CheckboxTouchApplication.qml:70 msgid "Start new session" msgstr "Démarrer une nouvelle session" #: checkbox-touch.qml:547 msgid "Reports have been saved to your Documents folder" msgstr "Les rapports ont été enregistrés dans votre dossier Documents" #: checkbox-touch.qml:548 checkbox-touch.qml:567 checkbox-touch.qml:573 #: components/CbtDialogLogic.js:5 components/PasswordDialog.qml:60 #: components/InputDialog.qml:58 msgid "OK" msgstr "OK" #: checkbox-touch.qml:548 msgid "View report" msgstr "Afficher le rapport" #: checkbox-touch.qml:566 msgid "Report has been submited.\n" msgstr "Le rapport a été soumis.\n" #: checkbox-touch.qml:571 msgid "Could not submit report. Reason:\n" msgstr "Impossible de soumettre le rapport. Raison :\n" #: confinement/plainbox-confined-shell.qml:97 msgid "Sending report - you should not see this page :-)" msgstr "Envoi du rapport - vous ne devriez pas voir cette page :-)" #: components/WelcomePage.qml:35 msgid "System Testing" msgstr "Test du système" #: components/WelcomePage.qml:47 components/AboutPage.qml:39 msgid "About" msgstr "À propos" #: components/WelcomePage.qml:73 msgid "Checkbox is loading..." msgstr "Checkbox se charge..." #: components/QmlConfinedPage.qml:40 components/InteractIntroPage.qml:48 #: components/QmlNativePage.qml:38 components/ManualIntroPage.qml:42 msgid "Test Description" msgstr "Description du test" #: components/QmlConfinedPage.qml:98 msgid "Waiting for the test to finish" msgstr "Attente de la fin du test" #: components/QmlConfinedPage.qml:107 components/InteractIntroPage.qml:99 #: components/QmlNativePage.qml:65 components/ManualIntroPage.qml:57 msgid "Start the test" msgstr "Démarrer le test" #: components/ResumeSessionPage.qml:39 msgid "Resume session" msgstr "Reprendre la session" #: components/ResumeSessionPage.qml:79 msgid "Rerun last" msgstr "Relancer le dernier test" #: components/ResumeSessionPage.qml:98 components/SelectionPage.qml:36 #: components/ErrorLogic.js:11 components/UserInteractSummaryPage.qml:95 msgid "Continue" msgstr "Continuer" #: components/TestVerificationPage.qml:41 #: components/UserInteractSummaryPage.qml:41 msgid "Verification" msgstr "Vérification" #. TRANSLATORS: this string is on a button that marks the given test as passed #: components/TestVerificationPage.qml:72 msgid "Pass" msgstr "Succès" #. TRANSLATORS: this string is on a button that marks the given test as failed #: components/TestVerificationPage.qml:86 msgid "Fail" msgstr "Échec" #: components/CommandOutputPage.qml:46 msgid "Command output" msgstr "Sortie de la commande" #. TRANSLATORS: a verb (call to action) #: components/CommandOutputPage.qml:54 msgid "Copy" msgstr "Copier" #. TRANSLATORS: This is a label on the button that goes back a page #: components/CommandOutputPage.qml:98 msgid "Back" msgstr "Retour" #: components/PasswordDialog.qml:47 msgid "Enter password" msgstr "Saisissez le mot de passe" #: components/PasswordDialog.qml:53 msgid "password" msgstr "mot de passe" #: components/PasswordDialog.qml:72 components/InputDialog.qml:68 msgid "Cancel" msgstr "Annuler" #: components/SelectionPage.qml:89 msgid "Toggle selection" msgstr "Inverser la sélection" #: components/WebViewer.qml:28 msgid "Test Report" msgstr "Rapport de test" #: components/AutomatedTestPage.qml:39 msgid "Automated test" msgstr "Test automatique" #. TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" #: components/UserInteractSummaryPage.qml:60 msgid "This test " msgstr "Ce test " #: components/UserInteractSummaryPage.qml:77 msgid "PASSED" msgstr "A RÉUSSI" #: components/UserInteractSummaryPage.qml:77 msgid "FAILED" msgstr "A ÉCHOUÉ" #: components/UserInteractSummaryPage.qml:88 msgid "You can go back to rerun the test or continue to the next test." msgstr "" "Vous pouvez revenir en arrière pour relancer le test ou continuer avec le " "test suivant." #: components/actions/SkipAction.qml:33 msgid "Skip" msgstr "Sauter" #: components/actions/SkipAction.qml:36 msgid "Do you really want to skip this test?" msgstr "Voulez-vous vraiment sauter ce test ?" #: components/actions/AddCommentAction.qml:31 components/CommentsDialog.qml:54 msgid "Add comment" msgstr "Ajouter un commentaire" #: components/CommentsDialog.qml:66 msgid "Done" msgstr "Terminé" #: components/ConfirmationDialog.qml:50 msgid "Are you sure?" msgstr "Êtes-vous sûr(e) ?" #: components/ConfirmationDialog.qml:69 msgid "YES" msgstr "OUI" #: components/ConfirmationDialog.qml:78 msgid "NO" msgstr "NON" #: components/ConfirmationDialog.qml:93 msgid "Do not ask me this question again" msgstr "Ne plus me reposer cette question" #: components/ResultsPage.qml:34 msgid "Test Results" msgstr "Résultats du test" #: components/ResultsPage.qml:52 components/AboutPage.qml:81 msgid "Close" msgstr "Fermer" #: components/ResultsPage.qml:87 msgid "Summary" msgstr "Résumé" #: components/ResultsPage.qml:131 msgid "tests passed" msgstr "" #: components/ResultsPage.qml:142 msgid "tests failed" msgstr "" #: components/ResultsPage.qml:153 msgid "tests skipped" msgstr "" #: components/ResultsPage.qml:162 msgid "Save detailed report" msgstr "Enregistrer un rapport détaillé" #. TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" #: components/ResultsPage.qml:171 msgid "Submit results to " msgstr "Soumettre les résultats à " #: components/AboutPage.qml:74 msgid "version: " msgstr "version : " #: components/AboutPage.qml:75 msgid "Plainbox version: " msgstr "Version de Plainbox : " #: components/ErrorDialog.qml:43 msgid "Um, OK" msgstr "Hum, OK" #: components/ErrorDialog.qml:53 components/ErrorDialog.qml:61 msgid "Error encountered" msgstr "Erreur rencontrée" #: components/CheckboxTouchApplication.qml:55 msgid "Could not start a session. Reason:\n" msgstr "Impossible de démarrer une session. Raison :\n" #: components/CheckboxTouchApplication.qml:65 msgid "Could not resume session" msgstr "Impossible de reprendre la session" #: py/sudo_with_pass_ctrl.py:160 #, python-format msgid "job[%s] executing %r with env %r in cwd %r" msgstr "tâche[%s] exécutant %r avec env %r en cwd %r" checkbox-converged-1.2.4/po/checkbox-touch.pot0000664000175000017500000001612712643477065021311 0ustar sylvainsylvain# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-15 12:23+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: checkbox-touch.qml:70 msgid "Run Checkbox-Touch in autopilot-testing mode" msgstr "" #: checkbox-touch.qml:75 msgid "Write only warnings and errors to standard error" msgstr "" #: checkbox-touch.qml:81 msgid "Path to a file containing checkbox-touch settings" msgstr "" #. TRANSLATORS: %1 means program version, %2 repository revision and %3 #. date when the package was built #. TRANSLATORS: keep the '\n' characters at the end of each line #: checkbox-touch.qml:198 #, qt-format msgid "" "Welcome to Checkbox Touch\n" "Version: %1\n" "(%2 %3)" msgstr "" #: checkbox-touch.qml:278 msgid "" "Previous session did not finish completely.\n" "Do you want to rerun last test, continue to the next test, or start a new " "session?" msgstr "" #: checkbox-touch.qml:289 msgid "Select test plan" msgstr "" #: checkbox-touch.qml:316 msgid "Select categories" msgstr "" #: checkbox-touch.qml:345 msgid "Select tests" msgstr "" #: checkbox-touch.qml:346 components/WelcomePage.qml:79 #: components/WelcomePage.qml:117 msgid "Start testing" msgstr "" #: checkbox-touch.qml:373 msgid "Select tests to re-run" msgstr "" #: checkbox-touch.qml:375 msgid "Finish" msgstr "" #: checkbox-touch.qml:375 msgid "Re-run" msgstr "" #: checkbox-touch.qml:415 msgid "Could not create component '" msgstr "" #: checkbox-touch.qml:417 components/ErrorLogic.js:13 #: components/CheckboxTouchApplication.qml:57 msgid "Quit" msgstr "" #: checkbox-touch.qml:436 msgid "Could not resume session." msgstr "" #: checkbox-touch.qml:438 components/ResumeSessionPage.qml:117 #: components/CheckboxTouchApplication.qml:70 msgid "Start new session" msgstr "" #: checkbox-touch.qml:547 msgid "Reports have been saved to your Documents folder" msgstr "" #: checkbox-touch.qml:548 checkbox-touch.qml:567 checkbox-touch.qml:573 #: components/CbtDialogLogic.js:5 components/PasswordDialog.qml:60 #: components/InputDialog.qml:58 msgid "OK" msgstr "" #: checkbox-touch.qml:548 msgid "View report" msgstr "" #: checkbox-touch.qml:566 msgid "Report has been submited.\n" msgstr "" #: checkbox-touch.qml:571 msgid "Could not submit report. Reason:\n" msgstr "" #: confinement/plainbox-confined-shell.qml:97 msgid "Sending report - you should not see this page :-)" msgstr "" #: components/WelcomePage.qml:35 msgid "System Testing" msgstr "" #: components/WelcomePage.qml:47 components/AboutPage.qml:39 msgid "About" msgstr "" #: components/WelcomePage.qml:73 msgid "Checkbox is loading..." msgstr "" #: components/QmlConfinedPage.qml:40 components/InteractIntroPage.qml:48 #: components/QmlNativePage.qml:38 components/ManualIntroPage.qml:42 msgid "Test Description" msgstr "" #: components/QmlConfinedPage.qml:98 msgid "Waiting for the test to finish" msgstr "" #: components/QmlConfinedPage.qml:107 components/InteractIntroPage.qml:99 #: components/QmlNativePage.qml:65 components/ManualIntroPage.qml:57 msgid "Start the test" msgstr "" #: components/ResumeSessionPage.qml:39 msgid "Resume session" msgstr "" #: components/ResumeSessionPage.qml:79 msgid "Rerun last" msgstr "" #: components/ResumeSessionPage.qml:98 components/SelectionPage.qml:36 #: components/ErrorLogic.js:11 components/UserInteractSummaryPage.qml:95 msgid "Continue" msgstr "" #: components/TestVerificationPage.qml:41 #: components/UserInteractSummaryPage.qml:41 msgid "Verification" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as passed #: components/TestVerificationPage.qml:72 msgid "Pass" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as failed #: components/TestVerificationPage.qml:86 msgid "Fail" msgstr "" #: components/CommandOutputPage.qml:46 msgid "Command output" msgstr "" #. TRANSLATORS: a verb (call to action) #: components/CommandOutputPage.qml:54 msgid "Copy" msgstr "" #. TRANSLATORS: This is a label on the button that goes back a page #: components/CommandOutputPage.qml:98 msgid "Back" msgstr "" #: components/PasswordDialog.qml:47 msgid "Enter password" msgstr "" #: components/PasswordDialog.qml:53 msgid "password" msgstr "" #: components/PasswordDialog.qml:72 components/InputDialog.qml:68 msgid "Cancel" msgstr "" #: components/SelectionPage.qml:89 msgid "Toggle selection" msgstr "" #: components/WebViewer.qml:28 msgid "Test Report" msgstr "" #: components/AutomatedTestPage.qml:39 msgid "Automated test" msgstr "" #. TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" #: components/UserInteractSummaryPage.qml:60 msgid "This test " msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "PASSED" msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "FAILED" msgstr "" #: components/UserInteractSummaryPage.qml:88 msgid "You can go back to rerun the test or continue to the next test." msgstr "" #: components/actions/SkipAction.qml:33 msgid "Skip" msgstr "" #: components/actions/SkipAction.qml:36 msgid "Do you really want to skip this test?" msgstr "" #: components/actions/AddCommentAction.qml:31 components/CommentsDialog.qml:54 msgid "Add comment" msgstr "" #: components/CommentsDialog.qml:66 msgid "Done" msgstr "" #: components/ConfirmationDialog.qml:50 msgid "Are you sure?" msgstr "" #: components/ConfirmationDialog.qml:69 msgid "YES" msgstr "" #: components/ConfirmationDialog.qml:78 msgid "NO" msgstr "" #: components/ConfirmationDialog.qml:93 msgid "Do not ask me this question again" msgstr "" #: components/ResultsPage.qml:34 msgid "Test Results" msgstr "" #: components/ResultsPage.qml:52 components/AboutPage.qml:81 msgid "Close" msgstr "" #: components/ResultsPage.qml:87 msgid "Summary" msgstr "" #: components/ResultsPage.qml:131 msgid "tests passed" msgstr "" #: components/ResultsPage.qml:142 msgid "tests failed" msgstr "" #: components/ResultsPage.qml:153 msgid "tests skipped" msgstr "" #: components/ResultsPage.qml:162 msgid "Save detailed report" msgstr "" #. TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" #: components/ResultsPage.qml:171 msgid "Submit results to " msgstr "" #: components/AboutPage.qml:74 msgid "version: " msgstr "" #: components/AboutPage.qml:75 msgid "Plainbox version: " msgstr "" #: components/ErrorDialog.qml:43 msgid "Um, OK" msgstr "" #: components/ErrorDialog.qml:53 components/ErrorDialog.qml:61 msgid "Error encountered" msgstr "" #: components/CheckboxTouchApplication.qml:55 msgid "Could not start a session. Reason:\n" msgstr "" #: components/CheckboxTouchApplication.qml:65 msgid "Could not resume session" msgstr "" #: py/sudo_with_pass_ctrl.py:160 #, python-format msgid "job[%s] executing %r with env %r in cwd %r" msgstr "" checkbox-converged-1.2.4/po/zh_TW.po0000664000175000017500000001716712643477065017257 0ustar sylvainsylvain# Chinese (Traditional) translation for checkbox # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the checkbox package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: checkbox\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-10-15 12:23+0200\n" "PO-Revision-Date: 2015-10-14 03:56+0000\n" "Last-Translator: Po-Hsu Lin \n" "Language-Team: Chinese (Traditional) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-11-28 04:35+0000\n" "X-Generator: Launchpad (build 17850)\n" #: checkbox-touch.qml:70 msgid "Run Checkbox-Touch in autopilot-testing mode" msgstr "" #: checkbox-touch.qml:75 msgid "Write only warnings and errors to standard error" msgstr "" #: checkbox-touch.qml:81 msgid "Path to a file containing checkbox-touch settings" msgstr "" #. TRANSLATORS: %1 means program version, %2 repository revision and %3 #. date when the package was built #. TRANSLATORS: keep the '\n' characters at the end of each line #: checkbox-touch.qml:198 #, qt-format msgid "" "Welcome to Checkbox Touch\n" "Version: %1\n" "(%2 %3)" msgstr "" "歡迎使用 Checkbox Touch\n" "版本: %1\n" "(%2 %3)" #: checkbox-touch.qml:278 msgid "" "Previous session did not finish completely.\n" "Do you want to rerun last test, continue to the next test, or start a new " "session?" msgstr "" #: checkbox-touch.qml:289 msgid "Select test plan" msgstr "" #: checkbox-touch.qml:316 msgid "Select categories" msgstr "選擇測試類別" #: checkbox-touch.qml:345 msgid "Select tests" msgstr "選擇測試項目" #: checkbox-touch.qml:346 components/WelcomePage.qml:79 #: components/WelcomePage.qml:117 msgid "Start testing" msgstr "開始測試" #: checkbox-touch.qml:373 msgid "Select tests to re-run" msgstr "選擇需要重新測試的項目" #: checkbox-touch.qml:375 msgid "Finish" msgstr "" #: checkbox-touch.qml:375 msgid "Re-run" msgstr "" #: checkbox-touch.qml:415 msgid "Could not create component '" msgstr "" #: checkbox-touch.qml:417 components/ErrorLogic.js:13 #: components/CheckboxTouchApplication.qml:57 msgid "Quit" msgstr "" #: checkbox-touch.qml:436 msgid "Could not resume session." msgstr "" #: checkbox-touch.qml:438 components/ResumeSessionPage.qml:117 #: components/CheckboxTouchApplication.qml:70 msgid "Start new session" msgstr "" #: checkbox-touch.qml:547 msgid "Reports have been saved to your Documents folder" msgstr "" #: checkbox-touch.qml:548 checkbox-touch.qml:567 checkbox-touch.qml:573 #: components/CbtDialogLogic.js:5 components/PasswordDialog.qml:60 #: components/InputDialog.qml:58 msgid "OK" msgstr "OK" #: checkbox-touch.qml:548 msgid "View report" msgstr "檢視報告" #: checkbox-touch.qml:566 msgid "Report has been submited.\n" msgstr "報告已上傳完畢。\n" #: checkbox-touch.qml:571 msgid "Could not submit report. Reason:\n" msgstr "無法傳送報告。原因:\n" #: confinement/plainbox-confined-shell.qml:97 msgid "Sending report - you should not see this page :-)" msgstr "" #: components/WelcomePage.qml:35 msgid "System Testing" msgstr "" #: components/WelcomePage.qml:47 components/AboutPage.qml:39 msgid "About" msgstr "" #: components/WelcomePage.qml:73 msgid "Checkbox is loading..." msgstr "Checkbox 載入中..." #: components/QmlConfinedPage.qml:40 components/InteractIntroPage.qml:48 #: components/QmlNativePage.qml:38 components/ManualIntroPage.qml:42 msgid "Test Description" msgstr "" #: components/QmlConfinedPage.qml:98 msgid "Waiting for the test to finish" msgstr "" #: components/QmlConfinedPage.qml:107 components/InteractIntroPage.qml:99 #: components/QmlNativePage.qml:65 components/ManualIntroPage.qml:57 msgid "Start the test" msgstr "" #: components/ResumeSessionPage.qml:39 msgid "Resume session" msgstr "" #: components/ResumeSessionPage.qml:79 msgid "Rerun last" msgstr "" #: components/ResumeSessionPage.qml:98 components/SelectionPage.qml:36 #: components/ErrorLogic.js:11 components/UserInteractSummaryPage.qml:95 msgid "Continue" msgstr "" #: components/TestVerificationPage.qml:41 #: components/UserInteractSummaryPage.qml:41 msgid "Verification" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as passed #: components/TestVerificationPage.qml:72 msgid "Pass" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as failed #: components/TestVerificationPage.qml:86 msgid "Fail" msgstr "" #: components/CommandOutputPage.qml:46 msgid "Command output" msgstr "指令輸出" #. TRANSLATORS: a verb (call to action) #: components/CommandOutputPage.qml:54 msgid "Copy" msgstr "複製" #. TRANSLATORS: This is a label on the button that goes back a page #: components/CommandOutputPage.qml:98 msgid "Back" msgstr "" #: components/PasswordDialog.qml:47 msgid "Enter password" msgstr "輸入密碼" #: components/PasswordDialog.qml:53 msgid "password" msgstr "密碼" #: components/PasswordDialog.qml:72 components/InputDialog.qml:68 msgid "Cancel" msgstr "取消" #: components/SelectionPage.qml:89 msgid "Toggle selection" msgstr "" #: components/WebViewer.qml:28 msgid "Test Report" msgstr "測試報告" #: components/AutomatedTestPage.qml:39 msgid "Automated test" msgstr "" #. TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" #: components/UserInteractSummaryPage.qml:60 msgid "This test " msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "PASSED" msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "FAILED" msgstr "" #: components/UserInteractSummaryPage.qml:88 msgid "You can go back to rerun the test or continue to the next test." msgstr "" #: components/actions/SkipAction.qml:33 msgid "Skip" msgstr "" #: components/actions/SkipAction.qml:36 msgid "Do you really want to skip this test?" msgstr "您真的想略過本測試嗎?" #: components/actions/AddCommentAction.qml:31 components/CommentsDialog.qml:54 msgid "Add comment" msgstr "" #: components/CommentsDialog.qml:66 msgid "Done" msgstr "" #: components/ConfirmationDialog.qml:50 msgid "Are you sure?" msgstr "您確定嗎?" #: components/ConfirmationDialog.qml:69 msgid "YES" msgstr "" #: components/ConfirmationDialog.qml:78 msgid "NO" msgstr "" #: components/ConfirmationDialog.qml:93 msgid "Do not ask me this question again" msgstr "" #: components/ResultsPage.qml:34 msgid "Test Results" msgstr "測試結果" #: components/ResultsPage.qml:52 components/AboutPage.qml:81 msgid "Close" msgstr "" #: components/ResultsPage.qml:87 msgid "Summary" msgstr "" #: components/ResultsPage.qml:131 msgid "tests passed" msgstr "" #: components/ResultsPage.qml:142 msgid "tests failed" msgstr "" #: components/ResultsPage.qml:153 msgid "tests skipped" msgstr "" #: components/ResultsPage.qml:162 msgid "Save detailed report" msgstr "" #. TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" #: components/ResultsPage.qml:171 msgid "Submit results to " msgstr "將結果傳送至 " #: components/AboutPage.qml:74 msgid "version: " msgstr "版本: " #: components/AboutPage.qml:75 msgid "Plainbox version: " msgstr "Plainbox 版本: " #: components/ErrorDialog.qml:43 msgid "Um, OK" msgstr "" #: components/ErrorDialog.qml:53 components/ErrorDialog.qml:61 msgid "Error encountered" msgstr "" #: components/CheckboxTouchApplication.qml:55 msgid "Could not start a session. Reason:\n" msgstr "" #: components/CheckboxTouchApplication.qml:65 msgid "Could not resume session" msgstr "" #: py/sudo_with_pass_ctrl.py:160 #, python-format msgid "job[%s] executing %r with env %r in cwd %r" msgstr "" checkbox-converged-1.2.4/po/pl.po0000664000175000017500000002151712643477065016631 0ustar sylvainsylvain# Checkbox Touch translations # Copyright (C) 2014 Canonical # This file is distributed under the same license as the checkbox-touch package. # Zygmunt , 2014. # msgid "" msgstr "" "Project-Id-Version: checkbox-touch 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-15 12:23+0200\n" "PO-Revision-Date: 2015-10-15 21:57+0000\n" "Last-Translator: Małgorzata Mularz \n" "Language-Team: polski <>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-11-28 04:35+0000\n" "X-Generator: Launchpad (build 17850)\n" "Language: Polish\n" "X-Language: pl_PL\n" #: checkbox-touch.qml:70 msgid "Run Checkbox-Touch in autopilot-testing mode" msgstr "Uruchom Checkbox-Touch w trybie testów autopilota" #: checkbox-touch.qml:75 msgid "Write only warnings and errors to standard error" msgstr "" "Wypisz wyłącznie ostrzeżenia i komunikaty o błędach do standardowego wyjścia " "błędu" #: checkbox-touch.qml:81 msgid "Path to a file containing checkbox-touch settings" msgstr "Ścieżka do pliku z ustawieniami checkbox-touch" #. TRANSLATORS: %1 means program version, %2 repository revision and %3 #. date when the package was built #. TRANSLATORS: keep the '\n' characters at the end of each line #: checkbox-touch.qml:198 #, qt-format msgid "" "Welcome to Checkbox Touch\n" "Version: %1\n" "(%2 %3)" msgstr "" "Witaj w aplikacji Checkbox Touch\n" "Wersja: %1\n" "(%2 %3)" #: checkbox-touch.qml:278 msgid "" "Previous session did not finish completely.\n" "Do you want to rerun last test, continue to the next test, or start a new " "session?" msgstr "" "Poprzednia sesja nie została zakończona.\n" "Czy chcesz ponownie uruchomić poprzedni test, przejść do następnego testu " "lub rozpocząć nową sesję?" #: checkbox-touch.qml:289 msgid "Select test plan" msgstr "Wybierz plan testów" #: checkbox-touch.qml:316 msgid "Select categories" msgstr "Wybierz kategorie" #: checkbox-touch.qml:345 msgid "Select tests" msgstr "Wybierz testy" #: checkbox-touch.qml:346 components/WelcomePage.qml:79 #: components/WelcomePage.qml:117 msgid "Start testing" msgstr "Rozpocznij test" #: checkbox-touch.qml:373 msgid "Select tests to re-run" msgstr "Wybierz testy do ponownego uruchomienia" #: checkbox-touch.qml:375 msgid "Finish" msgstr "Zakończ" #: checkbox-touch.qml:375 msgid "Re-run" msgstr "Uruchom ponownie" #: checkbox-touch.qml:415 msgid "Could not create component '" msgstr "Nie udało się utworzyć komponentu '" #: checkbox-touch.qml:417 components/ErrorLogic.js:13 #: components/CheckboxTouchApplication.qml:57 msgid "Quit" msgstr "Wyjdź" #: checkbox-touch.qml:436 msgid "Could not resume session." msgstr "Nie udało się wznowić sesji" #: checkbox-touch.qml:438 components/ResumeSessionPage.qml:117 #: components/CheckboxTouchApplication.qml:70 msgid "Start new session" msgstr "Rozpocznij nową sesję" #: checkbox-touch.qml:547 msgid "Reports have been saved to your Documents folder" msgstr "Raporty zostały zapisane w folderze Dokumenty" #: checkbox-touch.qml:548 checkbox-touch.qml:567 checkbox-touch.qml:573 #: components/CbtDialogLogic.js:5 components/PasswordDialog.qml:60 #: components/InputDialog.qml:58 msgid "OK" msgstr "OK" #: checkbox-touch.qml:548 msgid "View report" msgstr "Wyświetl raport" #: checkbox-touch.qml:566 msgid "Report has been submited.\n" msgstr "Raport wysłany.\n" #: checkbox-touch.qml:571 msgid "Could not submit report. Reason:\n" msgstr "Nie udało się wysłać raportu. Powód:\n" #: confinement/plainbox-confined-shell.qml:97 msgid "Sending report - you should not see this page :-)" msgstr "Wysyłanie raportu - nie powinieneś widzieć tej strony :-)" #: components/WelcomePage.qml:35 msgid "System Testing" msgstr "Testowanie systemu" #: components/WelcomePage.qml:47 components/AboutPage.qml:39 msgid "About" msgstr "O aplikacji" #: components/WelcomePage.qml:73 msgid "Checkbox is loading..." msgstr "Ładowanie Checkboksa" #: components/QmlConfinedPage.qml:40 components/InteractIntroPage.qml:48 #: components/QmlNativePage.qml:38 components/ManualIntroPage.qml:42 msgid "Test Description" msgstr "Opis testu" #: components/QmlConfinedPage.qml:98 msgid "Waiting for the test to finish" msgstr "Oczekiwanie na zakończenie testu" #: components/QmlConfinedPage.qml:107 components/InteractIntroPage.qml:99 #: components/QmlNativePage.qml:65 components/ManualIntroPage.qml:57 msgid "Start the test" msgstr "Rozpocznij test" #: components/ResumeSessionPage.qml:39 msgid "Resume session" msgstr "Wznów sesję" #: components/ResumeSessionPage.qml:79 msgid "Rerun last" msgstr "Uruchom ponownie" #: components/ResumeSessionPage.qml:98 components/SelectionPage.qml:36 #: components/ErrorLogic.js:11 components/UserInteractSummaryPage.qml:95 msgid "Continue" msgstr "Przejdź do następnego" #: components/TestVerificationPage.qml:41 #: components/UserInteractSummaryPage.qml:41 msgid "Verification" msgstr "Weryfikacja" #. TRANSLATORS: this string is on a button that marks the given test as passed #: components/TestVerificationPage.qml:72 msgid "Pass" msgstr "Powodzenie" #. TRANSLATORS: this string is on a button that marks the given test as failed #: components/TestVerificationPage.qml:86 msgid "Fail" msgstr "Niepowodzenie" #: components/CommandOutputPage.qml:46 msgid "Command output" msgstr "Wyjście polecenia" #. TRANSLATORS: a verb (call to action) #: components/CommandOutputPage.qml:54 msgid "Copy" msgstr "Kopiuj" #. TRANSLATORS: This is a label on the button that goes back a page #: components/CommandOutputPage.qml:98 msgid "Back" msgstr "Wstecz" #: components/PasswordDialog.qml:47 msgid "Enter password" msgstr "Wpisz hasło" #: components/PasswordDialog.qml:53 msgid "password" msgstr "hasło" #: components/PasswordDialog.qml:72 components/InputDialog.qml:68 msgid "Cancel" msgstr "Anuluj" #: components/SelectionPage.qml:89 msgid "Toggle selection" msgstr "Przełącz zaznaczenie" #: components/WebViewer.qml:28 msgid "Test Report" msgstr "Raport testów" #: components/AutomatedTestPage.qml:39 msgid "Automated test" msgstr "Automatyczny test" #. TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" #: components/UserInteractSummaryPage.qml:60 msgid "This test " msgstr "Ten test " #: components/UserInteractSummaryPage.qml:77 msgid "PASSED" msgstr "POWIÓDŁ SIĘ" #: components/UserInteractSummaryPage.qml:77 msgid "FAILED" msgstr "NIE POWIÓDŁ SIĘ" #: components/UserInteractSummaryPage.qml:88 msgid "You can go back to rerun the test or continue to the next test." msgstr "" "Możesz wrócić i ponownie uruchomić test lub przejść do następnego testu." #: components/actions/SkipAction.qml:33 msgid "Skip" msgstr "Pomiń" #: components/actions/SkipAction.qml:36 msgid "Do you really want to skip this test?" msgstr "Czy na pewno chcesz pominąć ten test?" #: components/actions/AddCommentAction.qml:31 components/CommentsDialog.qml:54 msgid "Add comment" msgstr "Dodaj komentarz" #: components/CommentsDialog.qml:66 msgid "Done" msgstr "Gotowe" #: components/ConfirmationDialog.qml:50 msgid "Are you sure?" msgstr "Na pewno?" #: components/ConfirmationDialog.qml:69 msgid "YES" msgstr "TAK" #: components/ConfirmationDialog.qml:78 msgid "NO" msgstr "NIE" #: components/ConfirmationDialog.qml:93 msgid "Do not ask me this question again" msgstr "Nie pytaj o to ponownie" #: components/ResultsPage.qml:34 msgid "Test Results" msgstr "Wyniki testów" #: components/ResultsPage.qml:52 components/AboutPage.qml:81 msgid "Close" msgstr "Zamknij" #: components/ResultsPage.qml:87 msgid "Summary" msgstr "Podsumowanie" #: components/ResultsPage.qml:131 msgid "tests passed" msgstr "testów się powiodło" #: components/ResultsPage.qml:142 msgid "tests failed" msgstr "testów się nie powiodło" #: components/ResultsPage.qml:153 msgid "tests skipped" msgstr "testów pominięto" #: components/ResultsPage.qml:162 msgid "Save detailed report" msgstr "Zapisz szczegółowy raport" #. TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" #: components/ResultsPage.qml:171 msgid "Submit results to " msgstr "Wyślij wyniki do: " #: components/AboutPage.qml:74 msgid "version: " msgstr "wersja: " #: components/AboutPage.qml:75 msgid "Plainbox version: " msgstr "Wersja Plainboksa: " #: components/ErrorDialog.qml:43 msgid "Um, OK" msgstr "Yyy, OK" #: components/ErrorDialog.qml:53 components/ErrorDialog.qml:61 msgid "Error encountered" msgstr "Wystąpił błąd" #: components/CheckboxTouchApplication.qml:55 msgid "Could not start a session. Reason:\n" msgstr "Nie udało się rozpocząć sesji. Powód:\n" #: components/CheckboxTouchApplication.qml:65 msgid "Could not resume session" msgstr "Nie udało się wznowić sesji" #: py/sudo_with_pass_ctrl.py:160 #, python-format msgid "job[%s] executing %r with env %r in cwd %r" msgstr "zadanie[%s] wykonywane %r w środowisku %r w katalogu %r" checkbox-converged-1.2.4/po/es.po0000664000175000017500000001631312643477065016623 0ustar sylvainsylvain# Spanish translation for checkbox # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the checkbox package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: checkbox\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-10-15 12:23+0200\n" "PO-Revision-Date: 2015-04-05 09:48+0000\n" "Last-Translator: Adolfo Jayme \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-11-28 04:35+0000\n" "X-Generator: Launchpad (build 17850)\n" #: checkbox-touch.qml:70 msgid "Run Checkbox-Touch in autopilot-testing mode" msgstr "" #: checkbox-touch.qml:75 msgid "Write only warnings and errors to standard error" msgstr "" #: checkbox-touch.qml:81 msgid "Path to a file containing checkbox-touch settings" msgstr "" #. TRANSLATORS: %1 means program version, %2 repository revision and %3 #. date when the package was built #. TRANSLATORS: keep the '\n' characters at the end of each line #: checkbox-touch.qml:198 #, qt-format msgid "" "Welcome to Checkbox Touch\n" "Version: %1\n" "(%2 %3)" msgstr "" #: checkbox-touch.qml:278 msgid "" "Previous session did not finish completely.\n" "Do you want to rerun last test, continue to the next test, or start a new " "session?" msgstr "" #: checkbox-touch.qml:289 msgid "Select test plan" msgstr "" #: checkbox-touch.qml:316 msgid "Select categories" msgstr "" #: checkbox-touch.qml:345 msgid "Select tests" msgstr "" #: checkbox-touch.qml:346 components/WelcomePage.qml:79 #: components/WelcomePage.qml:117 msgid "Start testing" msgstr "" #: checkbox-touch.qml:373 msgid "Select tests to re-run" msgstr "" #: checkbox-touch.qml:375 msgid "Finish" msgstr "" #: checkbox-touch.qml:375 msgid "Re-run" msgstr "" #: checkbox-touch.qml:415 msgid "Could not create component '" msgstr "" #: checkbox-touch.qml:417 components/ErrorLogic.js:13 #: components/CheckboxTouchApplication.qml:57 msgid "Quit" msgstr "" #: checkbox-touch.qml:436 msgid "Could not resume session." msgstr "" #: checkbox-touch.qml:438 components/ResumeSessionPage.qml:117 #: components/CheckboxTouchApplication.qml:70 msgid "Start new session" msgstr "" #: checkbox-touch.qml:547 msgid "Reports have been saved to your Documents folder" msgstr "" #: checkbox-touch.qml:548 checkbox-touch.qml:567 checkbox-touch.qml:573 #: components/CbtDialogLogic.js:5 components/PasswordDialog.qml:60 #: components/InputDialog.qml:58 msgid "OK" msgstr "" #: checkbox-touch.qml:548 msgid "View report" msgstr "" #: checkbox-touch.qml:566 msgid "Report has been submited.\n" msgstr "" #: checkbox-touch.qml:571 msgid "Could not submit report. Reason:\n" msgstr "" #: confinement/plainbox-confined-shell.qml:97 msgid "Sending report - you should not see this page :-)" msgstr "" #: components/WelcomePage.qml:35 msgid "System Testing" msgstr "" #: components/WelcomePage.qml:47 components/AboutPage.qml:39 msgid "About" msgstr "" #: components/WelcomePage.qml:73 msgid "Checkbox is loading..." msgstr "" #: components/QmlConfinedPage.qml:40 components/InteractIntroPage.qml:48 #: components/QmlNativePage.qml:38 components/ManualIntroPage.qml:42 msgid "Test Description" msgstr "" #: components/QmlConfinedPage.qml:98 msgid "Waiting for the test to finish" msgstr "" #: components/QmlConfinedPage.qml:107 components/InteractIntroPage.qml:99 #: components/QmlNativePage.qml:65 components/ManualIntroPage.qml:57 msgid "Start the test" msgstr "" #: components/ResumeSessionPage.qml:39 msgid "Resume session" msgstr "" #: components/ResumeSessionPage.qml:79 msgid "Rerun last" msgstr "" #: components/ResumeSessionPage.qml:98 components/SelectionPage.qml:36 #: components/ErrorLogic.js:11 components/UserInteractSummaryPage.qml:95 msgid "Continue" msgstr "" #: components/TestVerificationPage.qml:41 #: components/UserInteractSummaryPage.qml:41 msgid "Verification" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as passed #: components/TestVerificationPage.qml:72 msgid "Pass" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as failed #: components/TestVerificationPage.qml:86 msgid "Fail" msgstr "" #: components/CommandOutputPage.qml:46 msgid "Command output" msgstr "" #. TRANSLATORS: a verb (call to action) #: components/CommandOutputPage.qml:54 msgid "Copy" msgstr "" #. TRANSLATORS: This is a label on the button that goes back a page #: components/CommandOutputPage.qml:98 msgid "Back" msgstr "" #: components/PasswordDialog.qml:47 msgid "Enter password" msgstr "" #: components/PasswordDialog.qml:53 msgid "password" msgstr "" #: components/PasswordDialog.qml:72 components/InputDialog.qml:68 msgid "Cancel" msgstr "" #: components/SelectionPage.qml:89 msgid "Toggle selection" msgstr "" #: components/WebViewer.qml:28 msgid "Test Report" msgstr "" #: components/AutomatedTestPage.qml:39 msgid "Automated test" msgstr "" #. TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" #: components/UserInteractSummaryPage.qml:60 msgid "This test " msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "PASSED" msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "FAILED" msgstr "" #: components/UserInteractSummaryPage.qml:88 msgid "You can go back to rerun the test or continue to the next test." msgstr "" #: components/actions/SkipAction.qml:33 msgid "Skip" msgstr "" #: components/actions/SkipAction.qml:36 msgid "Do you really want to skip this test?" msgstr "" #: components/actions/AddCommentAction.qml:31 components/CommentsDialog.qml:54 msgid "Add comment" msgstr "" #: components/CommentsDialog.qml:66 msgid "Done" msgstr "" #: components/ConfirmationDialog.qml:50 msgid "Are you sure?" msgstr "" #: components/ConfirmationDialog.qml:69 msgid "YES" msgstr "" #: components/ConfirmationDialog.qml:78 msgid "NO" msgstr "" #: components/ConfirmationDialog.qml:93 msgid "Do not ask me this question again" msgstr "" #: components/ResultsPage.qml:34 msgid "Test Results" msgstr "" #: components/ResultsPage.qml:52 components/AboutPage.qml:81 msgid "Close" msgstr "" #: components/ResultsPage.qml:87 msgid "Summary" msgstr "" #: components/ResultsPage.qml:131 msgid "tests passed" msgstr "" #: components/ResultsPage.qml:142 msgid "tests failed" msgstr "" #: components/ResultsPage.qml:153 msgid "tests skipped" msgstr "" #: components/ResultsPage.qml:162 msgid "Save detailed report" msgstr "" #. TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" #: components/ResultsPage.qml:171 msgid "Submit results to " msgstr "" #: components/AboutPage.qml:74 msgid "version: " msgstr "" #: components/AboutPage.qml:75 msgid "Plainbox version: " msgstr "" #: components/ErrorDialog.qml:43 msgid "Um, OK" msgstr "" #: components/ErrorDialog.qml:53 components/ErrorDialog.qml:61 msgid "Error encountered" msgstr "" #: components/CheckboxTouchApplication.qml:55 msgid "Could not start a session. Reason:\n" msgstr "" #: components/CheckboxTouchApplication.qml:65 msgid "Could not resume session" msgstr "" #: py/sudo_with_pass_ctrl.py:160 #, python-format msgid "job[%s] executing %r with env %r in cwd %r" msgstr "" checkbox-converged-1.2.4/po/hu.po0000664000175000017500000001632312643477065016631 0ustar sylvainsylvain# Hungarian translation for checkbox # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the checkbox package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: checkbox\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-10-15 12:23+0200\n" "PO-Revision-Date: 2014-09-02 07:21+0000\n" "Last-Translator: Richard Somlói \n" "Language-Team: Hungarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-11-28 04:35+0000\n" "X-Generator: Launchpad (build 17850)\n" #: checkbox-touch.qml:70 msgid "Run Checkbox-Touch in autopilot-testing mode" msgstr "" #: checkbox-touch.qml:75 msgid "Write only warnings and errors to standard error" msgstr "" #: checkbox-touch.qml:81 msgid "Path to a file containing checkbox-touch settings" msgstr "" #. TRANSLATORS: %1 means program version, %2 repository revision and %3 #. date when the package was built #. TRANSLATORS: keep the '\n' characters at the end of each line #: checkbox-touch.qml:198 #, qt-format msgid "" "Welcome to Checkbox Touch\n" "Version: %1\n" "(%2 %3)" msgstr "" #: checkbox-touch.qml:278 msgid "" "Previous session did not finish completely.\n" "Do you want to rerun last test, continue to the next test, or start a new " "session?" msgstr "" #: checkbox-touch.qml:289 msgid "Select test plan" msgstr "" #: checkbox-touch.qml:316 msgid "Select categories" msgstr "" #: checkbox-touch.qml:345 msgid "Select tests" msgstr "" #: checkbox-touch.qml:346 components/WelcomePage.qml:79 #: components/WelcomePage.qml:117 msgid "Start testing" msgstr "" #: checkbox-touch.qml:373 msgid "Select tests to re-run" msgstr "" #: checkbox-touch.qml:375 msgid "Finish" msgstr "" #: checkbox-touch.qml:375 msgid "Re-run" msgstr "" #: checkbox-touch.qml:415 msgid "Could not create component '" msgstr "" #: checkbox-touch.qml:417 components/ErrorLogic.js:13 #: components/CheckboxTouchApplication.qml:57 msgid "Quit" msgstr "" #: checkbox-touch.qml:436 msgid "Could not resume session." msgstr "" #: checkbox-touch.qml:438 components/ResumeSessionPage.qml:117 #: components/CheckboxTouchApplication.qml:70 msgid "Start new session" msgstr "" #: checkbox-touch.qml:547 msgid "Reports have been saved to your Documents folder" msgstr "" #: checkbox-touch.qml:548 checkbox-touch.qml:567 checkbox-touch.qml:573 #: components/CbtDialogLogic.js:5 components/PasswordDialog.qml:60 #: components/InputDialog.qml:58 msgid "OK" msgstr "" #: checkbox-touch.qml:548 msgid "View report" msgstr "" #: checkbox-touch.qml:566 msgid "Report has been submited.\n" msgstr "" #: checkbox-touch.qml:571 msgid "Could not submit report. Reason:\n" msgstr "" #: confinement/plainbox-confined-shell.qml:97 msgid "Sending report - you should not see this page :-)" msgstr "" #: components/WelcomePage.qml:35 msgid "System Testing" msgstr "" #: components/WelcomePage.qml:47 components/AboutPage.qml:39 msgid "About" msgstr "" #: components/WelcomePage.qml:73 msgid "Checkbox is loading..." msgstr "" #: components/QmlConfinedPage.qml:40 components/InteractIntroPage.qml:48 #: components/QmlNativePage.qml:38 components/ManualIntroPage.qml:42 msgid "Test Description" msgstr "" #: components/QmlConfinedPage.qml:98 msgid "Waiting for the test to finish" msgstr "" #: components/QmlConfinedPage.qml:107 components/InteractIntroPage.qml:99 #: components/QmlNativePage.qml:65 components/ManualIntroPage.qml:57 msgid "Start the test" msgstr "" #: components/ResumeSessionPage.qml:39 msgid "Resume session" msgstr "" #: components/ResumeSessionPage.qml:79 msgid "Rerun last" msgstr "" #: components/ResumeSessionPage.qml:98 components/SelectionPage.qml:36 #: components/ErrorLogic.js:11 components/UserInteractSummaryPage.qml:95 msgid "Continue" msgstr "" #: components/TestVerificationPage.qml:41 #: components/UserInteractSummaryPage.qml:41 msgid "Verification" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as passed #: components/TestVerificationPage.qml:72 msgid "Pass" msgstr "" #. TRANSLATORS: this string is on a button that marks the given test as failed #: components/TestVerificationPage.qml:86 msgid "Fail" msgstr "" #: components/CommandOutputPage.qml:46 msgid "Command output" msgstr "" #. TRANSLATORS: a verb (call to action) #: components/CommandOutputPage.qml:54 msgid "Copy" msgstr "" #. TRANSLATORS: This is a label on the button that goes back a page #: components/CommandOutputPage.qml:98 msgid "Back" msgstr "" #: components/PasswordDialog.qml:47 msgid "Enter password" msgstr "" #: components/PasswordDialog.qml:53 msgid "password" msgstr "" #: components/PasswordDialog.qml:72 components/InputDialog.qml:68 msgid "Cancel" msgstr "" #: components/SelectionPage.qml:89 msgid "Toggle selection" msgstr "" #: components/WebViewer.qml:28 msgid "Test Report" msgstr "" #: components/AutomatedTestPage.qml:39 msgid "Automated test" msgstr "" #. TRANSLATORS: this string will be followed by either "PASSED" or "FAILED" #: components/UserInteractSummaryPage.qml:60 msgid "This test " msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "PASSED" msgstr "" #: components/UserInteractSummaryPage.qml:77 msgid "FAILED" msgstr "" #: components/UserInteractSummaryPage.qml:88 msgid "You can go back to rerun the test or continue to the next test." msgstr "" #: components/actions/SkipAction.qml:33 msgid "Skip" msgstr "" #: components/actions/SkipAction.qml:36 msgid "Do you really want to skip this test?" msgstr "" #: components/actions/AddCommentAction.qml:31 components/CommentsDialog.qml:54 msgid "Add comment" msgstr "" #: components/CommentsDialog.qml:66 msgid "Done" msgstr "" #: components/ConfirmationDialog.qml:50 msgid "Are you sure?" msgstr "" #: components/ConfirmationDialog.qml:69 msgid "YES" msgstr "" #: components/ConfirmationDialog.qml:78 msgid "NO" msgstr "" #: components/ConfirmationDialog.qml:93 msgid "Do not ask me this question again" msgstr "" #: components/ResultsPage.qml:34 msgid "Test Results" msgstr "" #: components/ResultsPage.qml:52 components/AboutPage.qml:81 msgid "Close" msgstr "" #: components/ResultsPage.qml:87 msgid "Summary" msgstr "" #: components/ResultsPage.qml:131 msgid "tests passed" msgstr "" #: components/ResultsPage.qml:142 msgid "tests failed" msgstr "" #: components/ResultsPage.qml:153 msgid "tests skipped" msgstr "" #: components/ResultsPage.qml:162 msgid "Save detailed report" msgstr "" #. TRANSLATORS: follwing string will be followed by a service name, e.g. "certification website" #: components/ResultsPage.qml:171 msgid "Submit results to " msgstr "" #: components/AboutPage.qml:74 msgid "version: " msgstr "" #: components/AboutPage.qml:75 msgid "Plainbox version: " msgstr "" #: components/ErrorDialog.qml:43 msgid "Um, OK" msgstr "" #: components/ErrorDialog.qml:53 components/ErrorDialog.qml:61 msgid "Error encountered" msgstr "" #: components/CheckboxTouchApplication.qml:55 msgid "Could not start a session. Reason:\n" msgstr "" #: components/CheckboxTouchApplication.qml:65 msgid "Could not resume session" msgstr "" #: py/sudo_with_pass_ctrl.py:160 #, python-format msgid "job[%s] executing %r with env %r in cwd %r" msgstr "" checkbox-converged-1.2.4/Makefile0000664000175000017500000000260112643477065016671 0ustar sylvainsylvain# More information: https://wiki.ubuntu.com/Touch/Testing # # Notes for autopilot tests: # ----------------------------------------------------------- # In order to run autopilot tests: # sudo apt-add-repository ppa:autopilot/ppa # sudo apt-get update # sudo apt-get install python-autopilot autopilot-qt ############################################################# TRANSLATION_ROOT=. APP_ID=com.ubuntu.checkbox MO_FILES=$(foreach infile,$(basename $(notdir $(wildcard ./po/*.po))),$(TRANSLATION_ROOT)/share/locale/$(infile)/LC_MESSAGES/$(APP_ID).mo) QMLJS_FILES:=$(shell find . -name "*.qml" -o -name "*.js" | grep -v ./tests) PY_FILES=$(wildcard ./py/*.py) build-me get-libs all: autopilot: ./get-libs chmod +x tests/autopilot/run tests/autopilot/run check: qmltestrunner -qt=5 -input tests/unit run: /usr/bin/qmlscene $@ checkbox-touch.qml build-translations: $(MO_FILES) echo $(MO_FILES) po: mkdir -p $(CURDIR)/po po/checkbox-touch.pot: $(QMLJS_FILES) $(PY_FILES) | po xgettext -o po/checkbox-touch.pot --qt --c++ --add-comments=TRANSLATORS --keyword=tr --keyword=tr:1,2 $(QMLJS_FILES) --from-code=UTF-8 xgettext -o po/checkbox-touch.pot --join-existing --language=python --add-comments=TRANSLATORS --keyword=_ --keyword=N_ $(PY_FILES) $(TRANSLATION_ROOT)/share/locale/%/LC_MESSAGES/$(APP_ID).mo: po/%.po mkdir -p $(TRANSLATION_ROOT)/share/locale/$*/LC_MESSAGES && msgfmt -o $@ $^ checkbox-converged-1.2.4/tests/0000775000175000017500000000000012643477065016374 5ustar sylvainsylvaincheckbox-converged-1.2.4/tests/unit/0000775000175000017500000000000012643477065017353 5ustar sylvainsylvaincheckbox-converged-1.2.4/tests/unit/tst_latchbutton.qml0000664000175000017500000000441212643477065023310 0ustar sylvainsylvain/* * This file is part of Checkbox * * Copyright 2014 Canonical Ltd. * * Authors: * - Maciej Kisielewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.0 import QtTest 1.0 import Ubuntu.Components 0.1 import "../../components" Item { /* This test checks if only one latchedClicked is emited even when multiple clicked calls are made to the underlaying button */ TestCase{ LatchButton { id: lb_latching onLatchedClicked: { tc_test_latching.counter++ } } id: tc_test_latching property var counter: 0 function test_latching() { for (var i = 0; i < 10; i++) { lb_latching.clicked() } compare(1, counter, "latchedClicked should be signalled signalled 1 time. Got " + counter + " times.") } } /* This test checks if latchedClicked signal is emited next time button is clicked after unlatch method was called. */ TestCase{ LatchButton { id: lb_unlatching onLatchedClicked: { tc_test_unlatching.counter++ } } id: tc_test_unlatching property var counter: 0 function test_unlatching() { for (var i = 0; i < 3; i++) { lb_unlatching.clicked() } lb_unlatching.unlatch() for (var i = 0; i < 3; i++) { lb_unlatching.clicked() } compare(2, counter, "latchedClicked should be signalled signalled 2 times. Got " + counter + " times.") } } } checkbox-converged-1.2.4/tests/autopilot/0000775000175000017500000000000012643477065020414 5ustar sylvainsylvaincheckbox-converged-1.2.4/tests/autopilot/run0000775000175000017500000000036112643477065021146 0ustar sylvainsylvain#!/bin/bash if [[ -z `which autopilot3` ]]; then echo "Autopilot is not installed. Skip" exit 1 fi SCRIPTPATH=`dirname $0` pushd ${SCRIPTPATH} python3 `which autopilot3` run --timeout-profile=long checkbox_touch ret=$? popd exit $ret checkbox-converged-1.2.4/tests/autopilot/autopilot-provider/0000775000175000017500000000000012643477065024264 5ustar sylvainsylvaincheckbox-converged-1.2.4/tests/autopilot/autopilot-provider/manage.py0000775000175000017500000000155412643477065026076 0ustar sylvainsylvain#!/usr/bin/env python3 from plainbox.provider_manager import setup, N_ # You can inject other stuff here but please don't go overboard. # # In particular, if you need comprehensive compilation support to get # your bin/ populated then please try to discuss that with us in the # upstream project IRC channel #checkbox on irc.freenode.net. # NOTE: one thing that you could do here, that makes a lot of sense, # is to compute version somehow. This may vary depending on the # context of your provider. Future version of PlainBox will offer git, # bzr and mercurial integration using the versiontools library # (optional) setup( name='2015.com.canonical.certification:autopilot-touch', version="1.0", description=N_( "The 2015.com.canonical.certification:autopilot-touch provider"), gettext_domain="2015_com_canonical_certification_autopilot-touch", ) checkbox-converged-1.2.4/tests/autopilot/autopilot-provider/data/0000775000175000017500000000000012643477065025175 5ustar sylvainsylvaincheckbox-converged-1.2.4/tests/autopilot/autopilot-provider/data/qml-job.qml0000664000175000017500000000203112643477065027245 0ustar sylvainsylvainimport QtQuick 2.0 import Ubuntu.Components 0.1 import QtQuick.Layouts 1.1 import Plainbox 0.1 QmlJob { id: root Component.onCompleted: testingShell.pageStack.push(testPage) Page { id: testPage objectName: "qmlTestPage" ColumnLayout { spacing: units.gu(10) anchors { margins: units.gu(5) fill: parent } Button { objectName: "passButton" Layout.fillWidth: true; Layout.fillHeight: true text: i18n.tr("Pass") color: "#38B44A" onClicked: { testDone({'outcome': 'pass'}); } } Button { objectName: "failButton" Layout.fillWidth: true; Layout.fillHeight: true text: i18n.tr("Fail") color: "#DF382C" onClicked: { testDone({"outcome": "fail"}); } } } } } checkbox-converged-1.2.4/tests/autopilot/autopilot-provider/units/0000775000175000017500000000000012643477065025426 5ustar sylvainsylvaincheckbox-converged-1.2.4/tests/autopilot/autopilot-provider/units/autopilot.pxu0000664000175000017500000002171212643477065030207 0ustar sylvainsylvainunit: test plan _description: Test plan that should be default one run by autopilot. id: checkbox-touch-autopilot _name: Checkbox-Touch autpoilot self-test estimated_duration: 60 include: autopilot/.* unit: category id: normal _name: Tests not requiring sudo id: autopilot/automated-test-that-fails _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test should be failed automatically. plugin: shell command: false estimated_duration: 0.1 flags: preserve-locale category_id: normal id: autopilot/automated-test-that-passes _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test should be passed automatically. plugin: shell command: true estimated_duration: 0.1 flags: preserve-locale category_id: normal # Next three tests aid Autopilot in testing manual jobs. There are 3 instances # of what may seem like the same job, but they exist so Autopilot can pass one # of them, fail second one, and skip the last one. id: autopilot/manual-1 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should pass this test. _steps: This manual test should be operated by Autopilot. Here, steps that test requires to take should be presented. _verification: This manual test should be operated by Autopilot. On this screen Autopilot should select an option that passes the test. plugin: manual estimated_duration: 2 flags: preserve-locale category_id: normal id: autopilot/manual-2 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should fail this test. _steps: This manual test should be operated by Autopilot. Here, steps that test requires to take should be presented. _verification: This manual test should be operated by Autopilot. On this screen Autopilot should select an option that fails the test. plugin: manual estimated_duration: 2 flags: preserve-locale category_id: normal id: autopilot/manual-3 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should skip this test. _steps: This manual test should be operated by Autopilot. Here, steps that test requires to take should be presented. _verification: This manual test should be operated by Autopilot. On this screen Autopilot should select an option that skips the test. plugin: manual estimated_duration: 2 flags: preserve-locale category_id: normal # Next three tests aid Autopilot in testing user-interact-verify jobs. # There are 3 instances of what may seem like the same job, but they exist so # Autopilot can pass one of them, fail second one, and skip the last one. id: autopilot/user-interact-verify-1 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should pass this test. _steps: This user-interact-verify test should be operated by Autopilot. Here, steps that test requires to take should be presented. _verification: On this screen Autopilot should select an option that passes the test. plugin: user-interact-verify estimated_duration: 2 category_id: normal flags: preserve-locale command: true id: autopilot/user-interact-verify-2 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should fail this test. _steps: This user-interact-verify test should be operated by Autopilot. Here, steps that test requires to take should be presented. _verification: On this screen Autopilot should select an option that fail the test. plugin: user-interact-verify estimated_duration: 2 category_id: normal flags: preserve-locale command: false id: autopilot/user-interact-verify-3 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should skip this test. _steps: This user-interact-verify test should be operated by Autopilot. Here, steps that test requires to take should be presented. _verification: On this screen Autopilot should select an option that skips the test. plugin: user-interact-verify estimated_duration: 2 category_id: normal flags: preserve-locale command: true # Next three tests aid Autopilot in testing user-verify jobs. # There are 3 instances of what may seem like the same job, but they exist so # Autopilot can pass one of them, fail second one, and skip the last one. id: autopilot/user-verify-1 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should pass this test. _verification: On this screen Autopilot should select an option that passes the test. plugin: user-verify estimated_duration: 1.5 category_id: normal flags: preserve-locale command: true id: autopilot/user-verify-2 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should fail this test. _verification: On this screen Autopilot should select an option that fails the test. plugin: user-verify estimated_duration: 1.5 category_id: normal flags: preserve-locale command: false id: autopilot/user-verify-3 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. Autopilot should skip this test. _verification: On this screen Autopilot should select an option that skips the test. plugin: user-verify estimated_duration: 1.5 category_id: normal flags: preserve-locale command: true # Next three tests aid Autopilot in testing user-interact jobs. # There are 3 jobs to test. One that fails and two that should pass, and one # from the latter group should be skipped by Autopilot test. id: autopilot/user-interact-1 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test should be passed automatically. _steps: This user-interact test should be operated by Autopilot. Here, steps that test requires to take should be presented. plugin: user-interact estimated_duration: 1.5 category_id: normal flags: preserve-locale command: true id: autopilot/user-interact-2 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test should be failed automatically. _steps: This user-interact test should be operated by Autopilot. Here, steps that test requires to take should be presented. plugin: user-interact estimated_duration: 1.5 category_id: normal flags: preserve-locale command: false id: autopilot/user-interact-3 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test should be skipped before command starts to run. _steps: This user-interact test should be operated by Autopilot. Here, steps that test requires to take should be presented. plugin: user-interact estimated_duration: 1.5 category_id: normal flags: preserve-locale command: true id: autopilot/print-and-verify _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test will print "foobar" on stdout and outopilot should verify if it was printed. plugin: user-verify command: echo "foobar" estimated_duration: 2 flags: preserve-locale category_id: normal id: autopilot/print-and-sleep _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test will print "foobar" on stdout and sleep for 10 seconds. plugin: shell command: echo "foobar" && sleep 10 estimated_duration: 10 flags: preserve-locale category_id: normal id: autopilot/resource-generator _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test will generate resources for other jobs to depend on. plugin: resource command: echo "resource_string: FooBar" estimated_duration: 1 flags: preserve-locale category_id: normal id: autopilot/resource-demanding-1 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test requires resource generated by the 'resource-generator' job to be present. If everything works as expected, the resource will be there and this job will run, resulting as "pass". plugin: shell command: true imports: from 2015.com.canonical.certification import autopilot/resource-generator as res_gen requires: res_gen.resource_string == "FooBar" estimated_duration: 1 flags: preserve-locale category_id: normal id: autopilot/resource-demanding-2 _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test requires resource that should not exist, therefore rendering this job as not-runnable and skipping it. plugin: shell command: true imports: from 2015.com.canonical.certification import autopilot/resource-generator as res_gen requires: res_gen.non_existent_resource == "SHOULDNT_BE_THERE" estimated_duration: 1 flags: preserve-locale category_id: normal id: autopilot/qml-job _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test runs qml-native job. plugin: qml qml_file: qml-job.qml flags: preserve-locale category_id: normal estimated_duration: 5 unit: category id: sudo _name: Tests run as root id: autopilot/sudo-right _purpose: Purpose of this test is to assist Autopilot in testing Checkbox-Touch. This test runs echo command as root. plugin: shell user: root flags: preserve-locale command: echo "foobar" category_id: sudo estimated_duration: 5 checkbox-converged-1.2.4/tests/autopilot/checkbox_touch/0000775000175000017500000000000012643477065023404 5ustar sylvainsylvaincheckbox-converged-1.2.4/tests/autopilot/checkbox_touch/test_checkbox_touch.py0000664000175000017500000003437312643477065030017 0ustar sylvainsylvain# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- from testtools.matchers import Equals from autopilot.matchers import Eventually from autopilot.input import Keyboard import checkbox_touch class TestCheckboxTouch(checkbox_touch.ClickAppTestCase): def skip_test(self, selectable_object_name=None): """Skip current test using header action. :param selectable_object_name: the objectName of component that has to be visible and selecable before the action is clicked. """ if selectable_object_name is not None: self.app.wait_select_single( objectName=selectable_object_name, visible=True) self.main_view.get_header().click_action_button('skip') dialog = self.app.wait_select_single(objectName='dialog') yes_btn = dialog.select_single(objectName='yesButton') self.pointing_device.click_object(yes_btn) keyboard = Keyboard.create('X11') comment_text = self.app.select_single(objectName='commentText') with keyboard.focused_type(comment_text) as kb: kb.type("Skipped by autopilot!") done_btn = self.app.select_single(objectName='doneButton') self.pointing_device.click_object(done_btn) def test_launches(self): main_view = self.app.select_single(objectName='mainView') self.assertThat(main_view.visible, Eventually(Equals(True))) def test_full_run(self): """ Test whether typical, full run of checkbox-touch works. This test launches checkbox-touch and runs all tests present in the autopilot provider. Tests that let user decide the outcome are served in three instances; one that user is supposed to pass, one that user should skip and the one that user should fail. The tests that have outcome determined automatically should come in two flavours; one that passes and one that fails. """ self.skipResumeIfShown() welcome_page = self.long_wait_select_single( self.app, objectName='welcomePage', state='loaded') start_btn = welcome_page.wait_select_single( objectName='startTestButton') self.pointing_device.click_object(start_btn) category_page = self.app.wait_select_single( objectName='categorySelectionPage', visible=True) self.main_view.get_header().click_action_button('toggleSelectionAction') category_id = '2015.com.canonical.certification::normal' list_item = category_page.wait_select_single( objectName='listItem', item_mod_id=category_id) self.pointing_device.click_object(list_item) continue_btn = category_page.wait_select_single( objectName='continueButton') self.pointing_device.click_object(continue_btn) tests_selection_page = self.app.wait_select_single( objectName='testSelectionPage', visible=True) continue_btn = tests_selection_page.wait_select_single( objectName='continueButton') self.pointing_device.click_object(continue_btn) # TWO automatic jobs should pass by: # autopilot/automated-test-that-fails # autopilot/automated-test-that-passes # now it's time for three manual jobs - autopilot/manual-{1,2,3} next_steps = [ ('manualIntroPage', 'continueButton'), ('testVerificationPage', 'passButton'), ('manualIntroPage', 'continueButton'), ('testVerificationPage', 'failButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) self.skip_test('manualIntroPage') # Now it's time for three UIV tests - # autopilot/user-interact-verify-{1,2,3} next_steps = [ ('userInteractVerifyIntroPage', 'startTestButton'), ('testVerificationPage', 'passButton'), ('userInteractVerifyIntroPage', 'startTestButton'), ('testVerificationPage', 'failButton') ] self.process_sequence_of_clicks_on_pages(next_steps) self.skip_test('userInteractVerifyIntroPage') # Next come 3 user-verify tests - autopilot/user-verify-{1,2,3} next_steps = [ ('userInteractVerifyIntroPage', 'startTestButton'), ('testVerificationPage', 'passButton'), ('userInteractVerifyIntroPage', 'startTestButton'), ('testVerificationPage', 'failButton') ] self.process_sequence_of_clicks_on_pages(next_steps) self.skip_test('userInteractVerifyIntroPage') # Now the user-interact tests - autopilot/user-interact-{1,2,3} next_steps = [ ('userInteractVerifyIntroPage', 'startTestButton'), ('userInteractSummary', 'continueButton'), ('userInteractVerifyIntroPage', 'startTestButton'), ('userInteractSummary', 'continueButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) self.skip_test('userInteractVerifyIntroPage') # Next is autopilot/print-and-verify next_steps = [ ('userInteractVerifyIntroPage', 'startTestButton'), ('testVerificationPage', 'passButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) # Next is a shell job that takes >10s to complete - # autopilot/print-and-verify # We have to use long_wait because wait_select_single would time-out. self.long_wait_select_single( self.app, objectName='qmlNativePage', visible=True) # Now, qml-native job already started. autpopilot/qml-job next_steps = [ ('qmlNativePage', 'continueButton'), ('qmlTestPage', 'passButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) # we should see results screen now results = {'passed': '10', 'failed': '5', 'skipped': '5'} self.check_results(results) class SessionResumeTests(checkbox_touch.ClickAppTestCase): def select_two_tests_and_quit(self): self.start_and_select_tests( '2015.com.canonical.certification::normal', [ '2015.com.canonical.certification::autopilot/user-verify-1', '2015.com.canonical.certification::autopilot/user-verify-2']) # make sure that test is shown (therefore session has been started) self.app.wait_select_single( objectName='userInteractVerifyIntroPage', visible=True) self.app.process.terminate() def test_rerun_after_resume(self): self.select_two_tests_and_quit() self.launch_application() self.assertThat(self.main_view.visible, Eventually(Equals(True))) # not doing long-wait, as the app was recently launched and it # *shouldn't* take long to relaunch it resume_page = self.app.wait_select_single( objectName='resumeSessionPage', visible=True) rerun_btn = resume_page.wait_select_single( objectName='rerunButton', visible=True) self.pointing_device.click_object(rerun_btn) intro_page = self.app.wait_select_single( objectName='userInteractVerifyIntroPage', visible=True) test_name_label = intro_page.wait_select_single( objectName='headerLabel', visible=True) self.assertThat(test_name_label.text, Eventually(Equals('autopilot/user-verify-1'))) def test_continue_after_resume(self): self.select_two_tests_and_quit() self.launch_application() self.assertThat(self.main_view.visible, Eventually(Equals(True))) resume_page = self.app.wait_select_single( objectName='resumeSessionPage', visible=True) continue_btn = resume_page.wait_select_single( objectName='continueButton') self.pointing_device.click_object(continue_btn) intro_page = self.app.wait_select_single( objectName='userInteractVerifyIntroPage', visible=True) test_name_label = intro_page.wait_select_single( objectName='headerLabel', visible=True) self.assertThat(test_name_label.text, Eventually(Equals('autopilot/user-verify-2'))) def test_restart_after_resume(self): self.select_two_tests_and_quit() self.launch_application() self.assertThat(self.main_view.visible, Eventually(Equals(True))) resume_page = self.app.wait_select_single( objectName='resumeSessionPage', visible=True) restart_btn = resume_page.wait_select_single( objectName='restartButton') self.pointing_device.click_object(restart_btn) welcome_page = self.app.wait_select_single( objectName='welcomePage') self.assertThat(welcome_page.visible, Eventually(Equals(True))) class CommandOutputTests(checkbox_touch.ClickAppTestCase): def test_output_visible_while_automated_test_is_running(self): self.start_and_select_tests( '2015.com.canonical.certification::normal', [ '2015.com.canonical.certification::autopilot/print-and-sleep']) page = self.app.wait_select_single( objectName='automatedTestPage', visible=True) button = page.wait_select_single( objectName='showOutputButton', visible=True) self.pointing_device.click_object(button) output_page = self.app.wait_select_single( objectName='commandOutputPage', visible=True) text_area = output_page.wait_select_single( objectName='textArea', visible=True) self.assertThat(text_area.text, Eventually(Equals("foobar\n"))) def test_output_visible_on_verification(self): self.start_and_select_tests( '2015.com.canonical.certification::normal', ['2015.com.canonical.certification::autopilot/print-and-verify']) intro_page = self.app.wait_select_single( objectName='userInteractVerifyIntroPage', visible=True) start_button = intro_page.wait_select_single( objectName='startTestButton', visible=True) self.pointing_device.click_object(start_button) verification_page = self.app.wait_select_single( objectName='testVerificationPage', visible=True) show_output_button = verification_page.wait_select_single( objectName='showOutputButton', visible=True) self.pointing_device.click_object(show_output_button) output_page = self.app.wait_select_single( objectName='commandOutputPage', visible=True) text_area = output_page.wait_select_single( objectName='textArea', visible=True) self.assertThat(text_area.text, Eventually(Equals("foobar\n"))) class RerunTests(checkbox_touch.ClickAppTestCase): def test_rerun_after_rerun(self): test_id = '2015.com.canonical.certification::autopilot/manual-2' self.start_and_select_tests( '2015.com.canonical.certification::normal', [test_id]) next_steps = [ ('manualIntroPage', 'continueButton'), ('testVerificationPage', 'failButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) results_page = self.app.wait_select_single( objectName='resultsPage', visible=True) self.main_view.get_header().click_action_button('rerunAction') # we now should see a re-run screen; let's select the only test rerun_page = self.app.wait_select_single( objectName='rerunSelectionPage', visible=True) list_item = rerun_page.wait_select_single( objectName='listItem', item_mod_id=test_id) self.pointing_device.click_object(list_item) continue_btn = rerun_page.wait_select_single( objectName='continueButton', visible=True) self.pointing_device.click_object(continue_btn) # run the same steps as before self.process_sequence_of_clicks_on_pages(next_steps) results_page = self.app.wait_select_single( objectName='resultsPage', visible=True) self.main_view.get_header().click_action_button('rerunAction') # we should see the re-run screen again rerun_page = self.app.wait_select_single( objectName='rerunSelectionPage', visible=True) continue_btn = rerun_page.wait_select_single( objectName='continueButton', visible=True) self.pointing_device.click_object(continue_btn) self.check_results({'passed': '0', 'failed': '1', 'skipped': '0'}) def test_rerun_after_fail(self): test_id = '2015.com.canonical.certification::autopilot/manual-2' self.start_and_select_tests( '2015.com.canonical.certification::normal', [test_id]) next_steps = [ ('manualIntroPage', 'continueButton'), ('testVerificationPage', 'failButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) results_page = self.app.wait_select_single( objectName='resultsPage', visible=True) self.main_view.get_header().click_action_button('rerunAction') # we now should see a re-run screen; let's select the only test rerun_page = self.app.wait_select_single( objectName='rerunSelectionPage', visible=True) list_item = rerun_page.wait_select_single( objectName='listItem', item_mod_id=test_id) self.pointing_device.click_object(list_item) continue_btn = rerun_page.wait_select_single( objectName='continueButton', visible=True) self.pointing_device.click_object(continue_btn) next_steps = [ ('manualIntroPage', 'continueButton'), ('testVerificationPage', 'passButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) # now set the outcome to 'pass'; we should be on results screen now self.check_results({'passed': '1', 'failed': '0', 'skipped': '0'}) def test_no_rerun_after_pass(self): test_id = '2015.com.canonical.certification::autopilot/manual-1' self.start_and_select_tests( '2015.com.canonical.certification::normal', [test_id]) next_steps = [ ('manualIntroPage', 'continueButton'), ('testVerificationPage', 'passButton'), ] self.process_sequence_of_clicks_on_pages(next_steps) self.check_results({'passed': '1', 'failed': '0', 'skipped': '0'}) checkbox-converged-1.2.4/tests/autopilot/checkbox_touch/test_sudo_tests.py0000664000175000017500000000626012643477065027215 0ustar sylvainsylvainimport os import stat import sys import tempfile import textwrap from autopilot.input import Keyboard import checkbox_touch class SudoTestCheckboxTouch(checkbox_touch.ClickAppTestCase): """ Tests requiring sudo password. This class mocks sudo command by providing an executable of that name in $PATH. This way the tests can run password dialogs in a controlled environment. The mock is set up when launching the app, this guarantees that overwriten $PATH gets propagated to the app. """ def tearDown(self): os.environ['PATH'] = self._original_path super().tearDown() def _launch_application_from_desktop(self): self.setup_mock() super()._launch_application_from_desktop() def setup_mock(self): self._original_path = os.environ['PATH'] self._tmpdir = tempfile.TemporaryDirectory() tmp_path = self._tmpdir.name mock_template = textwrap.dedent(""" #!/usr/bin/env python3 import sys expected_password = 'autopilot' given_password = sys.stdin.readlines()[0].strip('\\n') if given_password != expected_password: raise SystemExit(1) """).strip('\n') mock_file = os.path.join(tmp_path, 'sudo') with open(mock_file, 'wt') as f: f.write(mock_template) st = os.stat(mock_file) os.chmod(mock_file, st.st_mode | stat.S_IEXEC) os.environ['PATH'] = tmp_path + os.pathsep + self._original_path def test_smoke(self): test_id = '2015.com.canonical.certification::autopilot/sudo-right' self.start_and_select_tests( '2015.com.canonical.certification::sudo', [test_id]) keyboard = Keyboard.create('X11') password_box = self.app.wait_select_single(objectName='passwordBox') with keyboard.focused_type(password_box) as kb: kb.type("autopilot") ok_btn = self.app.wait_select_single(objectName='okButton') self.pointing_device.click_object(ok_btn) results = {'passed': '1', 'failed': '0', 'skipped': '0'} self.check_results(results) def test_wrong_password(self): test_id = '2015.com.canonical.certification::autopilot/sudo-right' self.start_and_select_tests( '2015.com.canonical.certification::sudo', [test_id]) keyboard = Keyboard.create('X11') password_box = self.app.wait_select_single(objectName='passwordBox') with keyboard.focused_type(password_box) as kb: kb.type("wrong") ok_btn = self.app.wait_select_single(objectName='okButton') self.pointing_device.click_object(ok_btn) results = {'passed': '0', 'failed': '1', 'skipped': '0'} self.check_results(results) def test_password_cancelled(self): test_id = '2015.com.canonical.certification::autopilot/sudo-right' self.start_and_select_tests( '2015.com.canonical.certification::sudo', [test_id]) cancel_btn = self.app.wait_select_single(objectName='cancelButton') self.pointing_device.click_object(cancel_btn) results = {'passed': '0', 'failed': '0', 'skipped': '1'} self.check_results(results) checkbox-converged-1.2.4/tests/autopilot/checkbox_touch/__init__.py0000664000175000017500000001673712643477065025533 0ustar sylvainsylvain# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- """Ubuntu Touch App autopilot tests.""" import os from autopilot import input, platform from autopilot.introspection.dbus import StateNotFoundError from autopilot.matchers import Eventually from testtools.matchers import Equals from ubuntuuitoolkit import base, emulators def _get_module_include_path(): return os.path.join(get_path_to_source_root(), 'modules') def get_path_to_source_root(): return os.path.abspath( os.path.join( os.path.dirname(__file__), '..', '..', '..', '..')) class ClickAppTestCase(base.UbuntuUIToolkitAppTestCase): """Common test case that provides several useful methods for the tests.""" package_id = '' # TODO app_name = 'checkbox-touch' def setUp(self): super(ClickAppTestCase, self).setUp() self.pointing_device = input.Pointer(self.input_device_class.create()) self.launch_application() self.assertThat(self.main_view.visible, Eventually(Equals(True))) def skipResumeIfShown(self): """Skip restart screen if presented.""" # this doesn't use 'long wait' helper, because a 'welcome page' may be # displayed and autopilot would take 5*timeout to figure it out retries = 5 while retries > 0: try: resume_page = self.app.wait_select_single( objectName='resumeSessionPage', visible=True) restart_btn = resume_page.wait_select_single( objectName='restartButton', visible=True) self.pointing_device.click_object(restart_btn) return except StateNotFoundError: pass try: self.app.wait_select_single( objectName='welcomePage', visible=True) return except StateNotFoundError: pass def start_and_select_tests(self, category_id, job_ids): self.skipResumeIfShown() welcome_page = self.long_wait_select_single( self.app, objectName='welcomePage', state='loaded') start_btn = welcome_page.wait_select_single( objectName='startTestButton') self.pointing_device.click_object(start_btn) category_page = self.app.wait_select_single( objectName='categorySelectionPage', visible=True) self.main_view.get_header().click_action_button('toggleSelectionAction') list_item = category_page.wait_select_single( objectName='listItem', item_mod_id=category_id) self.pointing_device.click_object(list_item) continue_btn = category_page.wait_select_single( objectName='continueButton', visible=True) self.pointing_device.click_object(continue_btn) test_selection_page = self.app.wait_select_single( objectName='testSelectionPage', visible=True) self.main_view.get_header().click_action_button('toggleSelectionAction') # lists are built dynamically, so there is a chance that proxies for # qml objects for list items that are down below are not yet present. # To make sure everything is loaded and ready, scroll to the bottom list_view = self.app.wait_select_single( objectName='listView', visible=True) list_view.swipe_to_bottom() for job_id in job_ids: list_item = test_selection_page.wait_select_single( objectName='listItem', item_mod_id=job_id) list_item.swipe_into_view() self.pointing_device.click_object(list_item) continue_btn = test_selection_page.wait_select_single( objectName='continueButton') self.pointing_device.click_object(continue_btn) def process_sequence_of_clicks_on_pages(self, steps): """ Do a sequence of clicks on simple page->component hierarchies. :param steps: sequence of (page-objectName, component-objectName) pairs to go through. Typical run of checkbox-touch requires user to go through a sequence of pages that have pass/fail buttons on them. This function helps go through a sequence like that. """ for parent, component in steps: self.app.wait_select_single( objectName=parent, visible=True) clickable = self.main_view.wait_select_single( objectName=component, visible=True) self.pointing_device.click_object(clickable) def check_results(self, results): results_page = self.app.wait_select_single( objectName='resultsPage', visible=True) lbl_passed = results_page.wait_select_single(objectName='passedLabel') self.assertThat(lbl_passed.text.startswith(results['passed']), Equals(True)) lbl_failed = results_page.wait_select_single(objectName='failedLabel') self.assertThat(lbl_failed.text.startswith(results['failed']), Equals(True)) lbl_skipped = results_page.wait_select_single( objectName='skippedLabel') self.assertThat(lbl_skipped.text.startswith(results['skipped']), Equals(True)) def launch_application(self): if platform.model() == 'Desktop': self._launch_application_from_desktop() else: self._launch_application_from_phablet() def long_wait_select_single(self, target, *args, **kwargs): """Try multiple times to do wait_select_single on target.""" retries = 5 while retries > 0: try: return getattr(target, 'wait_select_single')(*args, **kwargs) except StateNotFoundError: retries -= 1 continue raise StateNotFoundError(*args, **kwargs) def _launch_application_from_desktop(self): app_qml_source_location = self._get_app_qml_source_path() if os.path.exists(app_qml_source_location): self.app = self.launch_test_application( base.get_qmlscene_launch_command(), '-I', _get_module_include_path(), '-I', self._get_plainbox_qml_modules_path(), app_qml_source_location, '--autopilot', '--settings=""', app_type='qt', emulator_base=emulators.UbuntuUIToolkitEmulatorBase) else: raise NotImplementedError( "On desktop we can't install click packages yet, so we can " "only run from source.") def _get_app_qml_source_path(self): qml_file_name = '{0}.qml'.format(self.app_name) return os.path.join(self._get_path_to_app_source(), qml_file_name) def _get_path_to_app_source(self): return os.path.join(get_path_to_source_root(), self.app_name) def _get_plainbox_qml_modules_path(self): try: from plainbox.impl import get_plainbox_dir return os.path.join(get_plainbox_dir(), 'data', 'plainbox-qml-modules') except ImportError: return os.path.join(self._get_path_to_app_source(), 'lib', 'py', 'plainbox', 'data', 'plainbox-qml-modules') def _launch_application_from_phablet(self): # On phablet, we only run the tests against the installed click # package. self.app = self.launch_click_package(self.pacakge_id, self.app_name) @property def main_view(self): return self.app.select_single(emulators.MainView) checkbox-converged-1.2.4/README.rst0000664000175000017500000000731112643477065016723 0ustar sylvainsylvainThe (Converging) Checkbox ========================= This directory contains the implementation of Checkbox. The Ubuntu-SDK based touch application that initially, will target the phone form factor (but will work on all the form factors). It is implemented according to a design document (http://goo.gl/2agB51), that describes a minimum viable product for v1.0 The application works well in an x86 and armhf emulator as well as on the desktop. On a desktop system we recommend to use packaged dependencies (python3-plainbox and pyotherside). On any of the touch devices you will need to build a suitable click package (see below for details) or get a copy from the Ubuntu store, once it gets published. TL;DR ----- Run: .. code-block:: bash $ ./get-libs $ ./build-me --provider \ ../providers/plainbox-provider-certification-touch/ --install To start testing on the device! Getting dependencies -------------------- The click package is built from QML/JavaScript/Python code contained in the `py` and `components` directories. It also has `lib` directory that contains all necessary libraries needed to run checkbox. Before building click package make sure you run `./get-libs` to initialize and populate `./lib` directory. Use --get-local-plainbox to embed plainbox code that's available in the ../plainbox directory. Building and installing the click package ----------------------------------------- To build Checkbox-Touch click package run: .. code-block:: bash $ ./build-me to build and install the package run: .. code-block:: bash $ ./build-me --install Running Checkbox-Touch on a desktop ----------------------------------- To run on a desktop run `qmlscene checkbox-touch.qml` Note: Make sure you've ran `./get-libs` first. Choosing the default test-plan ------------------------------ If you wish to run one particular test plan, you may do so, by providing ./build-me script with --testplan option. E.g.: .. code-block:: bash $ ./build-me --testplan="2013.com.canonical.plainbox::stub" Embedding providers into click package -------------------------------------- If you wish to bundle providers into click package use `--provider` option in build-me script. E.g.: .. code-block:: bash $ ./build-me --provider ../providers/plainbox-provider-certification-touch Default Checkbox-Touch settings ------------------------------- During execution of `./build-me` script, `settings.json` file is generated. It contains values that Checkbox-Touch will use as its default ones. Altough not required, you may edit this file to suit your needs. Further assistance ------------------ For further assistance on packaging Checkbox, run: .. code-block:: bash $ ./build-me --help The Release Process =================== The release process is quite simple. It's also informal as we only did it once or twice - Tag the tree using the $PRODUCT-v$VERSION scheme (checkbox-touch-v0.1). - Build a new click package as outlined above. - Test it on some devices to ensure that's it's not horribly broken. - Create a release on an appropriate Launchpad milestone on the checkbox-touch project (https://launchpad.net/checkbox-touch). Write a changelog and upload the click package. The changelog should contain link to the version milestone on Launchpad, e.g. https://launchpad.net/checkbox-touch/+milestone/1.1.2 - Join ``#ubuntu-app-devel`` on freenode and figure out who can upload core-dev applications. Ask them to upload the new version. Popey declared to do the uploads, so start by asking him. that do this but it seems to be informal at this stage. - Do some post-release changes (bump the version in the sources). Commit that and propose a merge (along with the released tag) back to ``lp:checkbox``. checkbox-converged-1.2.4/.gitignore0000664000175000017500000000035612643477065017226 0ustar sylvainsylvain# Ignore external libraries lib/* # Ignore vendorized components components/vendor/ # Ignore auto-generated settings file settings.json # Ignore copied over providers providers/ # Ignore auto-generated desktop file checkbox-touch.desktop checkbox-converged-1.2.4/checkbox-touch.svg0000664000175000017500000001572112643477065020667 0ustar sylvainsylvain image/svg+xmlcheckbox-converged-1.2.4/AUTHORS0000664000175000017500000000321112645002025016255 0ustar sylvainsylvain

Ubuntu Touch Provider for Plainbox

Copyright:

Copyright C 2014 bq (MundoReader S.L.).

Copyright 2014-2015 Canonical Ltd.

Checkbox:

Authors:

- Ara Pulido <ara.pulido@canonical.com>

- Maciej Kisielewski <maciej.kisielewski@canonical.com>

- Sylvain Pineau <sylvain.pineau@canonical.com>

- Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

Copyright:

Copyright 2014-2015 Canonical Ltd. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 3.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. checkbox-converged-1.2.4/manifest.json0000664000175000017500000000105012643477065017727 0ustar sylvainsylvain{ "architecture": ["armhf", "i386", "amd64"], "description": "System testing utility for Ubuntu", "framework": "ubuntu-sdk-14.10", "hooks": { "checkbox-touch": { "apparmor": "checkbox-touch.json", "desktop": "checkbox-touch.desktop" } }, "maintainer": "Zygmunt Krynicki ", "name": "com.ubuntu.checkbox", "title": "Checkbox", "version": "1.2.4", "x-source": { "vcs-bzr": "lp:checkbox", "vcs-bzr-revno": "checkbox-touch-v1.2.4" } } checkbox-converged-1.2.4/checkbox-touch.qmlproject0000664000175000017500000000201112643477065022234 0ustar sylvainsylvainimport QmlProject 1.1 Project { mainFile: "checkbox-touch.qml" /* Include .qml, .js, and image files from current directory and subdirectories */ QmlFiles { directory: "." } JavaScriptFiles { directory: "." } ImageFiles { directory: "." } Files { filter: "*.desktop" } Files { filter: "www/*.html" } Files { filter: "Makefile" } Files { directory: "www" filter: "*" } Files { directory: "www/img/" filter: "*" } Files { directory: "www/css/" filter: "*" } Files { directory: "www/js/" filter: "*" } Files { directory: "tests/" filter: "*" } Files { directory: "debian" filter: "*" } Files { directory: "py" filter: "*.py" } /* List of plugin directories passed to QML runtime */ importPaths: [ "." ,"/usr/bin","/usr/lib/x86_64-linux-gnu/qt5/qml" ] }