[gnome-ostree] qa_smoketest: Initial test case that gdm started
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-ostree] qa_smoketest: Initial test case that gdm started
- Date: Sat, 12 Jan 2013 19:08:11 +0000 (UTC)
commit 9a40ef1fb5435d800b09d8d9483294a44b05579b
Author: Colin Walters <walters verbum org>
Date: Fri Jan 11 10:26:38 2013 -0500
qa_smoketest: Initial test case that gdm started
This script clones a VM and injects some testing bits, and then runs
it in qemu, watching the journal JSON for key message IDs.
Makefile-ostbuild.am | 2 +
Makefile-tests.am | 23 +++
Makefile.am | 2 +
src/ostbuild/js/builtins/qa_make_disk.js | 2 +-
src/ostbuild/js/builtins/qa_pull_deploy.js | 4 +-
src/ostbuild/js/builtins/qa_smoketest.js | 173 +++++++++++++++++--
src/ostbuild/js/libqa.js | 71 +++++++-
src/ostbuild/js/main.js | 3 +-
src/ostbuild/ostbuild.in | 1 +
src/tests/gnome-ostree-export-journal-to-serialdev | 2 +
...nome-ostree-export-journal-to-serialdev.service | 5 +
11 files changed, 256 insertions(+), 32 deletions(-)
---
diff --git a/Makefile-ostbuild.am b/Makefile-ostbuild.am
index 90f9979..3965096 100644
--- a/Makefile-ostbuild.am
+++ b/Makefile-ostbuild.am
@@ -45,6 +45,7 @@ jsostbuild_DATA= \
src/ostbuild/js/jsondb.js \
src/ostbuild/js/jsonutil.js \
src/ostbuild/js/main.js \
+ src/ostbuild/js/libqa.js \
src/ostbuild/js/guestfish.js \
src/ostbuild/js/params.js \
src/ostbuild/js/procutil.js \
@@ -62,6 +63,7 @@ jsostbuiltins_DATA= \
src/ostbuild/js/builtins/git_mirror.js \
src/ostbuild/js/builtins/qa_make_disk.js \
src/ostbuild/js/builtins/qa_pull_deploy.js \
+ src/ostbuild/js/builtins/qa_smoketest.js \
src/ostbuild/js/builtins/prefix.js \
src/ostbuild/js/builtins/resolve.js \
$(NULL)
diff --git a/Makefile-tests.am b/Makefile-tests.am
new file mode 100644
index 0000000..7c89d92
--- /dev/null
+++ b/Makefile-tests.am
@@ -0,0 +1,23 @@
+# Copyright (C) 2011 Colin Walters <walters verbum org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+if BUILDSYSTEM
+testdatadir = $(pkgdatadir)/tests
+testdata_DATA = src/tests/gnome-ostree-export-journal-to-serialdev \
+ src/tests/gnome-ostree-export-journal-to-serialdev.service \
+ $(NULL)
+endif
diff --git a/Makefile.am b/Makefile.am
index 4386e8b..f19a3f3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -39,7 +39,9 @@ libgsystem_cflags = $(GIO_UNIX_CFLAGS) -I$(srcdir)/src/libgsystem
libgsystem_libs = $(GIO_UNIX_LIBS)
include src/libgsystem/Makefile-libgsystem.am
privlib_LTLIBRARIES += libgsystem.la
+
include Makefile-ostbuild.am
+include Makefile-tests.am
include $(INTROSPECTION_MAKEFILE)
INTROSPECTION_GIRS = GSystem-1.0.gir
diff --git a/src/ostbuild/js/builtins/qa_make_disk.js b/src/ostbuild/js/builtins/qa_make_disk.js
index 0396170..163fa5f 100644
--- a/src/ostbuild/js/builtins/qa_make_disk.js
+++ b/src/ostbuild/js/builtins/qa_make_disk.js
@@ -51,7 +51,7 @@ const QaMakeDisk = new Lang.Class({
let guestfishProcess;
- ProcUtil.runSync(['qemu-img', 'create', tmppath.get_path(), '' + sizeMb + 'M'], cancellable);
+ ProcUtil.runSync(['qemu-img', 'create', '-f', 'qcow2', tmppath.get_path(), '' + sizeMb + 'M'], cancellable);
let makeDiskCmd = 'launch\n\
part-init /dev/vda mbr\n\
blockdev-getsize64 /dev/vda\n\
diff --git a/src/ostbuild/js/builtins/qa_pull_deploy.js b/src/ostbuild/js/builtins/qa_pull_deploy.js
index e2b88ca..183dc12 100644
--- a/src/ostbuild/js/builtins/qa_pull_deploy.js
+++ b/src/ostbuild/js/builtins/qa_pull_deploy.js
@@ -25,6 +25,7 @@ const GSystem = imports.gi.GSystem;
const ArgParse = imports.argparse;
const ProcUtil = imports.procutil;
+const LibQA = imports.libqa;
const GuestFish = imports.guestfish;
const loop = GLib.MainLoop.new(null, true);
@@ -151,8 +152,7 @@ initrd /%s\n', [args.osname, bootRelativeKernelPath, args.osname, bootRelativeIn
this._mntdir = this._workdir.get_child('mnt');
GSystem.file_ensure_directory(this._mntdir, true, cancellable);
- let gfmnt = new GuestFish.GuestMount(diskpath, {partitionOpts: ['-m', '/dev/sda3',
- '-m', '/dev/sda1:/boot'],
+ let gfmnt = new GuestFish.GuestMount(diskpath, {partitionOpts: LibQA.DEFAULT_GF_PARTITION_OPTS,
readWrite: true});
gfmnt.mount(this._mntdir, cancellable);
try {
diff --git a/src/ostbuild/js/builtins/qa_smoketest.js b/src/ostbuild/js/builtins/qa_smoketest.js
index f47d4b1..a86d212 100644
--- a/src/ostbuild/js/builtins/qa_smoketest.js
+++ b/src/ostbuild/js/builtins/qa_smoketest.js
@@ -25,50 +25,185 @@ const GSystem = imports.gi.GSystem;
const ArgParse = imports.argparse;
const ProcUtil = imports.procutil;
+const LibQA = imports.libqa;
const loop = GLib.MainLoop.new(null, true);
+const TIMEOUT_SECONDS = 2 * 60;
+
const QaSmokeTest = new Lang.Class({
Name: 'QaSmokeTest',
+ RequiredMessageIDs: ["39f53479d3a045ac8e11786248231fbf", // graphical.target
+ "f77379a8490b408bbe5f6940505a777b"], // systemd-journald
+
+ _onQemuExited: function(proc, result) {
+ let [success, status] = ProcUtil.asyncWaitCheckFinish(proc, result);
+ this._qemu = null;
+ loop.quit();
+ if (!success) {
+ this._failed = true;
+ print("Qemu exited with status " + status);
+ }
+ },
+
+ _onTimeout: function() {
+ print("Timeout reached");
+ this._failed = true;
+ loop.quit();
+ },
+
+ _onJournalOpen: function(file, result) {
+ try {
+ this._journalStream = file.read_finish(result);
+ this._journalDataStream = Gio.DataInputStream.new(this._journalStream);
+ this._openedJournal = true;
+ this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
+ Lang.bind(this, this._onJournalReadLine));
+ } catch (e) {
+ print("Open failed: " + e);
+ this._failed = true;
+ loop.quit();
+ }
+ },
+
+ _onJournalReadLine: function(stream, result) {
+ let line, len;
+ try {
+ [line, len] = stream.read_line_finish_utf8(result);
+ } catch (e) {
+ this._failed = true;
+ loop.quit();
+ throw e;
+ }
+ if (line) {
+ let data = JSON.parse(line);
+ let messageId = data['MESSAGE_ID'];
+ if (messageId) {
+ if (this._pendingRequiredMessageIds[messageId]) {
+ print("Found required message ID " + messageId);
+ delete this._pendingRequiredMessageIds[messageId];
+ this._countPendingRequiredMessageIds--;
+ }
+ }
+ if (this._countPendingRequiredMessageIds > 0) {
+ this._readingJournal = true;
+ this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
+ Lang.bind(this, this._onJournalReadLine));
+ } else {
+ print("Found all required message IDs, exiting");
+ loop.quit();
+ }
+ }
+ },
+
+ _onJournalChanged: function(monitor, file, otherFile, eventType) {
+ if (!this._openedJournal) {
+ this._openedJournal = true;
+ file.read_async(GLib.PRIORITY_DEFAULT,
+ this._cancellable,
+ Lang.bind(this, this._onJournalOpen));
+ } else if (!this._readingJournal) {
+ this._readingJournal = true;
+ this._journalDataStream.read_line_async(GLib.PRIORITY_DEFAULT, this._cancellable,
+ Lang.bind(this, this._onJournalReadLine));
+ }
+ },
+
execute: function(argv) {
let cancellable = null;
let parser = new ArgParse.ArgumentParser("Basic smoke testing via parsing serial console");
+ parser.addArgument('--monitor', { action: 'storeTrue' });
parser.addArgument('diskpath');
let args = parser.parse(argv);
- let diskpath = Gio.File.new_for_path(args.diskpath);
+ this._failed = false;
+ this._journalStream = null;
+ this._journalDataStream = null;
+ this._openedJournal = false;
+ this._readingJournal = false;
+ this._pendingRequiredMessageIds = {};
+ this._countPendingRequiredMessageIds = 0;
+ for (let i = 0; i < this.RequiredMessageIDs.length; i++) {
+ this._pendingRequiredMessageIds[this.RequiredMessageIDs[i]] = true;
+ this._countPendingRequiredMessageIds += 1;
+ }
+ this._cancellable = cancellable;
+ let srcDiskpath = Gio.File.new_for_path(args.diskpath);
let workdir = Gio.File.new_for_path('.');
+
+ let qemuArgs = [LibQA.getQemuPath()];
+ qemuArgs.push.apply(qemuArgs, LibQA.DEFAULT_QEMU_OPTS);
- let fallbackPaths = ['/usr/libexec/qemu-kvm']
- let qemuPathString = GLib.find_program_in_path('qemu-kvm');
- if (qemuPathString == null) {
- for (let i = 0; i < fallbackPaths.length; i++) {
- let path = Gio.File.new_for_path(fallbackPaths[i]);
- if (!path.query_exists(null))
- continue;
- qemuPathString = path.get_path();
- }
- }
- if (qemuPathString == null) {
- throw new Error("Unable to find qemu-kvm");
+ let diskClone = workdir.get_child('qa-smoketest.img');
+ GSystem.shutil_rm_rf(diskClone, cancellable);
+
+ LibQA.createDiskSnapshot(srcDiskpath, diskClone, cancellable);
+ let [gfmnt, mntdir] = LibQA.newReadWriteMount(diskClone, cancellable);
+ try {
+ LibQA.modifyBootloaderAppendKernelArgs(mntdir, ["console=ttyS0"], cancellable);
+
+ let [currentDir, currentEtcDir] = LibQA.getDeployDirs(mntdir, 'gnome-ostree');
+ let binDir = currentDir.resolve_relative_path('usr/bin');
+ // let systemdSystemDir = currentDir.resolve_relative_path('usr/lib/systemd/system');
+ let multiuserWantsDir = currentEtcDir.resolve_relative_path('systemd/system/multi-user.target.wants');
+
+ let datadir = Gio.File.new_for_path(GLib.getenv('OSTBUILD_DATADIR'));
+ let exportScript = datadir.resolve_relative_path('tests/gnome-ostree-export-journal-to-serialdev');
+ let exportScriptService = datadir.resolve_relative_path('tests/gnome-ostree-export-journal-to-serialdev.service');
+ let exportBin = binDir.get_child(exportScript.get_basename());
+ exportScript.copy(exportBin, 0, cancellable, null, null);
+ GSystem.file_chmod(exportBin, 493, cancellable);
+ exportScriptService.copy(multiuserWantsDir.get_child(exportScriptService.get_basename()), 0, cancellable, null, null);
+ } finally {
+ gfmnt.umount(cancellable);
}
- let qemuArgs = [qemuPathString, '-vga', 'std', 'm', '768M',
- '-usb', '-usbdevice', 'tablet',
- '-drive', 'file=' + diskpath + ',if=virtio',
- ];
+ let consoleOutput = Gio.File.new_for_path('console.out');
+ GSystem.shutil_rm_rf(consoleOutput, cancellable);
+ let journalOutput = Gio.File.new_for_path('journal-json.txt');
+ GSystem.shutil_rm_rf(journalOutput, cancellable);
+ qemuArgs.push.apply(qemuArgs, ['-drive', 'file=' + diskClone.get_path() + ',if=virtio',
+ '-vnc', 'none',
+ '-watchdog', 'ib700',
+ '-watchdog-action', 'poweroff',
+ '-serial', 'file:' + consoleOutput.get_path(),
+ '-device', 'virtio-serial',
+ '-chardev', 'file,id=journaljson,path=' + journalOutput.get_path(),
+ '-device', 'virtserialport,chardev=journaljson,name=org.gnome.journaljson']);
+ if (args.monitor)
+ qemuArgs.push.apply(qemuArgs, ['-monitor', 'stdio']);
+
let qemuContext = new GSystem.SubprocessContext({ argv: qemuArgs });
+ if (args.monitor)
+ qemuContext.set_stdin_disposition(GSystem.SubprocessStreamDisposition.INHERIT);
let qemu = new GSystem.Subprocess({context: qemuContext});
+ this._qemu = qemu;
print("starting qemu");
qemu.init(cancellable);
- qemu.wait_sync_check(cancellable);
+ qemu.wait(cancellable, Lang.bind(this, this._onQemuExited));
+
+ let journalMonitor = journalOutput.monitor_file(0, cancellable);
+ journalMonitor.connect('changed', Lang.bind(this, this._onJournalChanged));
+
+ GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, TIMEOUT_SECONDS,
+ Lang.bind(this, this._onTimeout));
+ loop.run();
+
+ if (this._qemu)
+ this._qemu.force_exit();
+
+ if (this._failed) {
+ print("Exiting abnormally");
+ return 1;
+ }
print("Complete!");
+ return 0;
}
});
@@ -76,7 +211,7 @@ function main(argv) {
let ecode = 1;
var app = new QaSmokeTest();
GLib.idle_add(GLib.PRIORITY_DEFAULT,
- function() { try { app.execute(argv); ecode = 0; } finally { loop.quit(); }; return false; });
+ function() { try { ecode = app.execute(argv); } finally { loop.quit(); }; return false; });
loop.run();
return ecode;
}
diff --git a/src/ostbuild/js/libqa.js b/src/ostbuild/js/libqa.js
index fd523c9..1b45378 100644
--- a/src/ostbuild/js/libqa.js
+++ b/src/ostbuild/js/libqa.js
@@ -24,15 +24,68 @@ const Params = imports.params;
const ProcUtil = imports.procutil;
const GuestFish = imports.guestfish;
-const ModifyBootloaderAppendKernelArg = new Lang.Class({
- Name: 'ModifyGrub',
- Extends: GuestFish.GuestFish,
+const DEFAULT_GF_PARTITION_OPTS = ['-m', '/dev/sda3', '-m', '/dev/sda1:/boot'];
+const DEFAULT_QEMU_OPTS = ['-vga', 'std', '-m', '768M',
+ '-usb', '-usbdevice', 'tablet',
+ '-smp', '1,sockets=1,cores=1,threads=1'];
- _init: function(diskpath, params) {
- this.parent(diskpath, params);
- },
-
- run: function(argsToAppend, cancellable) {
+
+function newReadWriteMount(diskpath, cancellable) {
+ let mntdir = Gio.File.new_for_path('mnt');
+ let gfmnt = new GuestFish.GuestMount(diskpath, {partitionOpts: DEFAULT_GF_PARTITION_OPTS,
+ readWrite: true});
+ gfmnt.mount(mntdir, cancellable);
+ return [gfmnt, mntdir];
+}
+
+function createDiskSnapshot(diskpath, newdiskpath, cancellable) {
+ ProcUtil.runSync(['qemu-img', 'create', '-f', 'qcow2', '-o', 'backing_file=' + diskpath.get_path(),
+ newdiskpath.get_path()], cancellable);
+}
+
+function getQemuPath() {
+ let fallbackPaths = ['/usr/libexec/qemu-kvm']
+ let qemuPathString = GLib.find_program_in_path('qemu-kvm');
+ qemuPathString = GLib.find_program_in_path('qemu-kvm');
+ if (!qemuPathString)
+ qemuPathString = GLib.find_program_in_path('kvm');
+ if (qemuPathString == null) {
+ for (let i = 0; i < fallbackPaths.length; i++) {
+ let path = Gio.File.new_for_path(fallbackPaths[i]);
+ if (!path.query_exists(null))
+ continue;
+ qemuPathString = path.get_path();
+ }
}
-});
+ if (qemuPathString == null) {
+ throw new Error("Unable to find qemu-kvm");
+ }
+ return qemuPathString;
+}
+
+function getDeployDirs(mntdir, osname) {
+ let basedir = mntdir.resolve_relative_path('ostree/deploy/' + osname);
+ return [basedir.get_child('current'),
+ basedir.get_child('current-etc')];
+}
+function modifyBootloaderAppendKernelArgs(mntdir, kernelArgs, cancellable) {
+ let grubConfPath = mntdir.resolve_relative_path('boot/grub/grub.conf');
+ let grubConf = GSystem.file_load_contents_utf8(grubConfPath, cancellable);
+ let lines = grubConf.split('\n');
+ let modifiedLines = [];
+
+ let kernelArg = kernelArgs.join(' ');
+ let kernelLineRe = /kernel \//;
+ for (let i = 0; i < lines.length; i++) {
+ let line = lines[i];
+ let match = kernelLineRe.exec(line);
+ if (!match)
+ modifiedLines.push(line);
+ else
+ modifiedLines.push(line + ' ' + kernelArg);
+ }
+ let modifiedGrubConf = modifiedLines.join('\n');
+ grubConfPath.replace_contents(modifiedGrubConf, null, false, Gio.FileCreateFlags.NONE,
+ cancellable);
+}
diff --git a/src/ostbuild/js/main.js b/src/ostbuild/js/main.js
index 4f4a319..6acda4a 100755
--- a/src/ostbuild/js/main.js
+++ b/src/ostbuild/js/main.js
@@ -24,7 +24,8 @@ const BUILTINS = {'autobuilder': "Run resolve and build",
'resolve': "Expand git revisions in source to exact targets",
'build': "Build multiple components and generate trees",
'qa-make-disk': "Generate a bare disk image",
- 'qa-pull-deploy': "Copy OSTree repo into virtual disk and deploy it"};
+ 'qa-pull-deploy': "Copy OSTree repo into virtual disk and deploy it",
+ 'qa-smoketest': "Basic smoke testing via parsing serial console"};
function usage(ecode) {
print("Builtins:");
diff --git a/src/ostbuild/ostbuild.in b/src/ostbuild/ostbuild.in
index a15136c..01d75bc 100755
--- a/src/ostbuild/ostbuild.in
+++ b/src/ostbuild/ostbuild.in
@@ -22,6 +22,7 @@ export GI_TYPELIB_PATH="@pkglibdir@/girepository-1.0${GI_TYPELIB_PATH:+:$GI_TYPE
export LD_LIBRARY_PATH="@pkglibdir@/${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
# Don't auto-spawn a session bus
export GIO_USE_VFS=local
+export OSTBUILD_DATADIR= pkgdatadir@
export OSTBUILD_LIBDIR= pkglibdir@
exec gjs -I "${jsdir}" "${jsdir}/main.js" "$@"
diff --git a/src/tests/gnome-ostree-export-journal-to-serialdev b/src/tests/gnome-ostree-export-journal-to-serialdev
new file mode 100755
index 0000000..d843f11
--- /dev/null
+++ b/src/tests/gnome-ostree-export-journal-to-serialdev
@@ -0,0 +1,2 @@
+#!/bin/bash
+exec journalctl -o json -b -f --no-tail > /dev/virtio-ports/org.gnome.journaljson
diff --git a/src/tests/gnome-ostree-export-journal-to-serialdev.service b/src/tests/gnome-ostree-export-journal-to-serialdev.service
new file mode 100644
index 0000000..2ed5c07
--- /dev/null
+++ b/src/tests/gnome-ostree-export-journal-to-serialdev.service
@@ -0,0 +1,5 @@
+[Unit]
+Description=QA: Copy bootup journal log to /dev/ttyS1
+
+[Service]
+ExecStart=/usr/bin/gnome-ostree-export-journal-to-serialdev
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]