[gnome-ostree] qa_smoketest: Initial test case that gdm started



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]