SF.net SVN: morphix:[2661] trunk/mmaker/utils

[email protected] Wed, 01 Jul 2009 19:24:58 +0000
Newsgroups gmane.linux.morphix.cvs
Message-ID <[email protected]>
Revision: 2661
          http://morphix.svn.sourceforge.net/morphix/?rev=2661&view=rev
Author:   bmsleight
Date:     2009-07-01 19:24:57 +0000 (Wed, 01 Jul 2009)

Log Message:
-----------
Adding Autotesting2. Python, xml and joy

Added Paths:
-----------
    trunk/mmaker/utils/autotesting2/
    trunk/mmaker/utils/autotesting2/README
    trunk/mmaker/utils/autotesting2/autotesting.py
    trunk/mmaker/utils/autotesting2/tests/
    trunk/mmaker/utils/autotesting2/tests/local-test_morphix-lightgui.xml
    trunk/mmaker/utils/autotesting2/tests/morphix-lightgui.xml

Added: trunk/mmaker/utils/autotesting2/README
===================================================================
--- trunk/mmaker/utils/autotesting2/README	                        (rev 0)
+++ trunk/mmaker/utils/autotesting2/README	2009-07-01 19:24:57 UTC (rev 2661)
@@ -0,0 +1,99 @@
+AUTOTESTING README
+==================
+
+autotesting --tests=TESTS.XML
+
+This readme outlines the different tags used in TESTS.XML. 
+
+EXAMPLE TESTS.XML
+=================
+
+<autotesting>
+  </tests>
+    <test> 
+      <download>http://127.0.0.1/autotesting/debian-live-501-i386-xfce-desktop.iso</download>
+      <title>Debian Live (xfce)</title>
+      <description>Debian Live. Daily build of squeeze xfce iso.</description>
+      <background>http://git.debian.org/?p=debian-live/homepage.git;a=blob_plain;f=images/debian-live.png</background>
+      <frequency>daily</frequency>
+      <qemu>
+        <xscreen>800x600x24</xscreen>
+        <binary>qemu</binary>
+        <options>-cdrom</options>
+        <pause>10</pause>
+        <sendkeys>kp_enter</sendkeys>
+        <!-- Time to run qmeu -->
+        <time>600</time>
+      </qemu>
+      <output>
+        <!-- Number of test sets to keep -->
+        <keep>4</keep>
+        <root>/home/bms/autotesting/tests/</root>
+        <local>example-live/squeeze/xfce/</local>
+        <video>autotesting.ogv</video>
+        <screenshots>
+          <final>final-screenshot.png</final>
+          <montage>montage-of-video-frames.png</montage>
+        </screenshots>
+      </output>
+    </test>
+  </tests>
+</autotesting>
+
+
+LOOKING AT EACH TAG
+===================
+
+<download>http://127.0.0.1/autotesting/debian-live-501-i386-xfce-desktop.iso</download>
+The location of the image to be tested. This can be an iso or an USB image (img) or in fact anything that can be booted with qemu.
+
+<title>Debian Live (xfce)</title>
+<description>Debian Live. Daily build of squeeze xfce iso.</description>
+The title and description tags are used in the opening titles of the video and also useful when scanning a long tests.xml file. 
+
+<background>http://git.debian.org/?p=debian-live/homepage.git;a=blob_plain;f=images/debian-live.png</background>
+The location of the background graphic used in the video. 
+
+<frequency>daily</frequency>
+This can be daily, weekly or monthly. Daily, means that the test will be run every time. Weekly - the test will only be done if the current day is a Sunday. Monthly - the test will only be done on the 1st day of a month, 
+
+<xscreen>800x600x24</xscreen>
+The width, height and colour depth of the xserver (Xvfb) in which to run qemu. Not at the minimum dimensions required for qemu are 800x600. 
+
+<binary>qemu</binary>
+The qemu binary. This could be for example qemu-ppc64 or qemu-arm, to test images for different architectures. 
+
+<options>-cdrom</options>
+The option to pass to qemu in conjunction with the image we are testing. In this example we are testing a iso, so we need the "-cdrom" option. For an img file you would use -hda.
+
+<pause>10</pause>
+Time to pause before sending keys presses.
+
+<sendkeys>kp_enter</sendkeys>
+Some iso and img require some user interaction to star the booting, normally just a enter being pressed. Useful for selecting different options from the grub menu. Use the qmeu sendkey command, send "kp_enter" - a keypad enter. To send multiple key presses, use a comma separated list. <sendkeys>down,down,kp_enter</sendkeys>. 
+
+<time>600</time>
+Let qemu run for 600 seconds.
+
+<keep>4</keep>
+Number of test sets to keep. If frequency is weekly, and keep is 4, then any test set older than 4 weeks will be deleted.
+
+<root>/home/bms/autotesting/tests/</root>
+<local>example-live/squeeze/xfce/</local>
+A new directory will be created at /root/prefix/DATE/, in this example /home/bms/autotesting/tests/example-live/squeeze/xfce/2009-07-01/ and the output files will be stored in this directory. Another directory /root/current/prefix/ will be created, with symlinks to the latest output files, e.g. /home/bms/autotesting/tests/current/example-live/squeeze/xfce/.
+
+<video>autotesting.ogv</video>
+The output video file stored in the output directories (see <root> and <local>). File is a theora video, created by the wonderful application - recordmydeskptop.
+
+<final>final-screenshot.png</final>
+<montage>montage-of-video-frames.png</montage>
+The output file names stored in the output directories (see <root> and <local>) of a final screenshot of qemu and a montage of frames from the video. These can give a good idea of how successful the test was rather than viewing the whole video. 
+
+
+REQUIRED APPLICATIONS
+=====================
+
+I will be trying to package autotesting - however for reference this following applications are required:-
+
+python, python-amara, wget, Xvfb, xloadimage, qemu, recordmydesktop, ffmpeg, imagemagick, gmessage
+

Added: trunk/mmaker/utils/autotesting2/autotesting.py
===================================================================
--- trunk/mmaker/utils/autotesting2/autotesting.py	                        (rev 0)
+++ trunk/mmaker/utils/autotesting2/autotesting.py	2009-07-01 19:24:57 UTC (rev 2661)
@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+#
+#   autotesting - automatically test by video qemu booting.
+#   Copyright (C) Brendan M. Sleight, et al. <[email protected]>
+#
+#   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, either version 2 of the License, or
+#   (at your option) any later version.
+#
+#   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/>.
+
+
+from optparse import OptionParser
+import amara, datetime, os, shutil, subprocess, telnetlib, tempfile, time
+
+def displayNumber():
+    """Really a global we can change later to something better.
+    like automaticaly get the next free display number. """
+    return ":8"
+    
+def log(message):
+    """ Print [date] message """ 
+    now = datetime.datetime.now()
+    print "[" + str(now) + "] " + str(message) 
+
+def cronCheak(frequency):
+    """Check if the frequency (e.g. daily, weekly, mounthly is due to 
+    be run at this time
+    returns boolean.
+    """
+    now = datetime.datetime.now()
+    if frequency == "Daily" or frequency == "daily":
+        return True
+    elif frequency == "Weekly" or frequency == "weekly":
+        if now.weekday() == 0:
+            return True
+    elif frequency == "Monthly" or frequency == "monthly":
+        if now.day == 1:
+            return True        
+    return False
+
+def dateBasedOnFrequency(frequency, keep):
+    now = datetime.datetime.now()
+    if frequency == "Daily" or frequency == "daily":
+        days=1 
+    elif frequency == "Weekly" or frequency == "weekly":
+        days=7 
+    elif frequency == "Monthly" or frequency == "monthly":
+        # No date delta of month so near enough ...
+        days=30
+    old = now - datetime.timedelta(days=days*keep)
+    return old
+
+def parseTest(xml):
+    """ Get the xml file and load as a object usign amara
+    returns amaraObject"""
+    tests = amara.parse(xml)
+    return tests.autotesting.tests
+
+def wget(url):
+    """ Use wget to download a file
+    return fileObject"""
+    log("Downloading " + url)
+    tmpFile = tempfile.NamedTemporaryFile(prefix="autotesting_wget_")
+    L = ['wget', '-nv', str(url), '-O', tmpFile.name]
+    # Maybe do something with the retcode in the future.
+    retcode = subprocess.call(L)
+    return tmpFile
+
+def getDownloads(downloadURL, backgroundURL):
+    """ Create two temp files and download the main downlaod and 
+    the background 
+    return tempfile, tempfile""" 
+    background = wget(str(backgroundURL))
+    download = wget(str(downloadURL))
+    return download, background
+
+def authorityFile():
+    """ Return a file containing "localhost"
+    """
+    authority = tempfile.NamedTemporaryFile(prefix="autotesting_authority_")
+    authority.write("localhost\n")
+    authority.close()
+    return authority
+
+def startXvfb(display, xscreen, background):
+    """Start a Xvfb instance. The host display for qemu to run inside.
+    then set the background image. Return ths PID of Xvfb
+    return interger"""
+    log("Starting Xvfb")
+    authority = authorityFile()
+    xvfbCommand = ["Xvfb", display, "-auth", authority.name, "-screen", "0", xscreen] 
+    xvfb = subprocess.Popen(xvfbCommand)
+    log("Setting Xvfb background")
+    xloadimage = ["xloadimage", "-display", display, "-onroot", "-fullscreen", background]
+    retcode = subprocess.call(xloadimage)
+    return xvfb
+
+def waitUntilEncodingFinished(recordMyDesktop):
+    log("Waiting for recordmydesktop encoding to finish")
+    while recordMyDesktop.poll()!=0:
+        print ".. ",
+        time.sleep(1)
+    print " "
+
+def kill(process, message):
+    log(message)
+    kill = ["kill", str(process.pid)]
+    retcode = subprocess.call(kill)
+
+def startRecordMyDesktop(display):
+    log("Starting Record My Desktop")
+    video = tempfile.NamedTemporaryFile(prefix="autotesting_video_", suffix=".ogv")
+    recordmydesktopCommand = ["recordmydesktop",  "--no-cursor", "-display", 
+                               display ,"--no-sound", "--overwrite", "-o" ,video.name]
+    recordmydesktop = subprocess.Popen(recordmydesktopCommand)
+    return recordmydesktop, video 
+
+def xmessage(display, title, message, time, font):
+    xloadimage = ["gmessage", "-display", display, "-timeout", time, 
+                "-font", font, "-button", "", "-title", title, 
+                "-center", "\n" + message]
+    retcode = subprocess.call(xloadimage)
+    
+def openingTitles(display, test):
+    log("Showing Opening Titles")
+    time.sleep(2)
+    xmessage(display, str(test.title), "Autotesting of: " + str(test.title), "3", "monospace 14")
+    xmessage(display, str(test.title), str(test.description), "3", "monospace 12")
+    xmessage(display, str(test.title), "Created at " + str(datetime.datetime.now()), "2", "monospace 10")
+
+def runningQemu(display, test, qemuDownload):
+    """ Start qemu running, with a local telnet port at 55555 listening acting as the qemu monitor
+    returns subprocess.process"""
+    telnet = ("127.0.0.1", "55555")
+    address, port = telnet
+    monitor = "telnet:" + address + ":" + port  + ",server,nowait"
+    qemuBinary = str(test.qemu.binary)
+    log("Starting " + qemuBinary)
+    if not qemuBinary.startswith("qemu"):
+        log("WARNING! : " + qemuBinary + " does not start with qemu, using qemu instead.")
+        qemuBinary = "qemu"
+    qemuCommand = [qemuBinary, "-monitor", monitor, "-full-screen", str(test.qemu.options), str(qemuDownload)]
+    qemu = subprocess.Popen(qemuCommand, env={"DISPLAY": display})
+    sendkeysToQemu(test, telnet)
+    log("Running qemu for  " + str(test.qemu.time) + " seconds")
+    time.sleep(int(str(test.qemu.time)))
+    return qemu
+
+def sendkeysToQemu(test, telnet):
+    """Using telnet to send commands to qemu monitor
+    """
+    address, port = telnet
+    log("Sending keys after pause of " + str(test.qemu.pause) + " seconds")
+    time.sleep(int(str(test.qemu.pause)))
+    tn = telnetlib.Telnet(address, port)
+    sendkeys = str(test.qemu.sendkeys)
+    for key in sendkeys.split(','):
+        log("qemu sendkey " + key)
+        tn.write("sendkey " + key + "\n")
+        time.sleep(1)
+
+
+def captureScreenshot(display):
+    """ Capture screen shot on display
+    return fileObject"""
+    log("Capture screenshot")
+    finalImage = tempfile.NamedTemporaryFile(prefix="autotesting_video_", suffix=".png")
+    captureCommand = ["import", "-display", display, "-window", "root", finalImage.name]
+    retcode = subprocess.call(captureCommand)
+    return finalImage
+
+def createMontage(video, test):
+    """ Make 16 frames of the video at 1/16, 2/16, 3/16 .... 16/16 of 
+        way through the video. The make a montage of these frames in to
+        one image. 
+    return fileObject"""
+    log("Creating Montage")
+    montage = tempfile.NamedTemporaryFile(prefix="autotesting_video_", suffix=".png")
+    videoLength = int(str(test.qemu.pause)) + int(str(test.qemu.time)) 
+    # Time for opening titles
+    videoLength = videoLength + 2 + 3 + 3 + 2
+    listFrames = []
+    listFramesNames = []
+    for count in range(16):
+        ss = 0.0625 * count * videoLength
+        frame = tempfile.NamedTemporaryFile(prefix="autotesting_frame_", suffix=".jpg")
+        frameName = frame.name + "%d.jpg"
+        frameNameOut = frame.name + "1.jpg"
+        ffmpeg = ["ffmpeg", "-i", video.name, "-an", "-ss", str(ss), "-t", 
+                  "01", "-r", "1", "-y", frameName]
+        retcode = subprocess.call(ffmpeg)
+        shutil.move(frameNameOut, frame.name)
+        listFramesNames.append(frame.name)
+        listFrames.append(frame)
+    montageCommand = ["montage", "-geometry", "180x135+4+4", "-frame", "5"]
+    for frame in listFramesNames:
+        montageCommand.append(frame)
+    montageCommand.append(montage.name)
+    retcode = subprocess.call(montageCommand)
+    return montage
+
+def storeFile(tmpFile, copyLocation, symLocation):
+    shutil.copyfile(tmpFile, copyLocation)
+    try:
+        os.remove(symLocation)
+    except:
+        pass
+    os.symlink(copyLocation, symLocation)
+
+def fileOutputs(test, video, finalImage, montage):
+    """ Move files to the correct place (as per XML tags)
+    including symlinks to current.
+    """
+    log("Moving files to the correct place.")
+    root=str(test.output.root)
+    local=str(test.output.local)
+    today=str(datetime.date.today())
+    mainDir = root + "/" + local + "/" + today + "/"
+    dateDirs = root + "/" + local + "/"
+    currentDir = root + "/current/" + local + "/"
+    try:
+        os.makedirs(mainDir)
+    except:
+        pass
+    try:
+        os.makedirs(currentDir)
+    except:
+        pass
+    storeFile(video.name, mainDir + str(test.output.video), 
+                                   currentDir + str(test.output.video))    
+    storeFile(finalImage.name, mainDir + str(test.output.screenshots.final), 
+                                   currentDir + str(test.output.screenshots.final))
+    storeFile(montage.name, mainDir + str(test.output.screenshots.montage), 
+                                   currentDir + str(test.output.screenshots.montage))
+    keepDate = dateBasedOnFrequency(str(test.frequency), int(str(test.output.keep)))
+    log("Removing old Autotesting output before " + keepDate.strftime("%A %B %d %I:%M:%S %p %Y"))
+    for f in os.listdir(dateDirs):
+        fp = dateDirs + f
+        if (time.mktime(keepDate.timetuple()) - os.path.getmtime(fp) ) > 0:
+            # Remove the old file
+            shutil.rmtree(fp)
+            log("Removing - " + fp)
+
+def main():
+    usage = "usage: %prog [options] --tests=TESTS.XML \n       %prog --help for all options"
+    parser = OptionParser(usage, version="%prog ")
+    parser.add_option("-t", "--tests", dest="tests",
+                      help="complete the autotesting definined in the xml template")
+    (options, args) = parser.parse_args() 
+    if not options.tests :
+        parser.error("Must pass a list of tests to complete.")
+        
+    # Main loop
+    display = displayNumber()
+    tests = parseTest(options.tests)
+    for test in tests.test:
+        if cronCheak(str(test.frequency)):
+            log("Starting Autotesting of: " + str(test.title))
+            (download, background) = getDownloads(str(test.download), str(test.background))
+            xvfb = startXvfb(display, str(test.qemu.xscreen), background.name)
+            (recordMyDesktop, video) = startRecordMyDesktop(display)
+            openingTitles(display, test)
+            qemu = runningQemu(display, test, download.name)
+            finalImage = captureScreenshot(display)
+            kill(qemu, "Killing qemu")
+            kill(recordMyDesktop, "Killing recordmysdesktop")
+            waitUntilEncodingFinished(recordMyDesktop)
+            kill(xvfb, "Killing Xvfb")
+            # Put video, montage in right place remvoe old versions etc.
+            montage = createMontage(video, test)
+            fileOutputs(test, video, finalImage, montage)
+            log("Finished Autotesting of: " + str(test.title))
+            log("*****************************")        
+
+if __name__ == "__main__":
+    main()

Added: trunk/mmaker/utils/autotesting2/tests/local-test_morphix-lightgui.xml
===================================================================
--- trunk/mmaker/utils/autotesting2/tests/local-test_morphix-lightgui.xml	                        (rev 0)
+++ trunk/mmaker/utils/autotesting2/tests/local-test_morphix-lightgui.xml	2009-07-01 19:24:57 UTC (rev 2661)
@@ -0,0 +1,32 @@
+<autotesting>
+  <tests>
+    <test>
+      <download>http://127.0.0.1/autotesting/lightgui-basemod-2.6.23-2008-02-10_0018-latest.iso</download>
+      <title>Morphix LightGUI</title>
+      <description>Morphix. Daily build of LightGUI iso.</description>
+      <background>http://www.morphix.org/templates/MorphixORG/images/mambo_header.jpg</background>
+      <frequency>daily</frequency>
+      <qemu>
+        <xscreen>800x600x24</xscreen>
+        <binary>qemu</binary>
+        <options>-cdrom</options>
+        <!-- Time to pause before sending keys presses -->
+        <pause>10</pause>
+        <sendkeys>down,down,kp_enter</sendkeys>
+        <!-- Time to run qmeu -->
+        <time>600</time>
+      </qemu>
+      <output>
+        <!-- Number of test sets to keep -->
+        <keep>4</keep>
+        <root>/home/bms/autotesting/tests/</root>
+        <local>morphix/lightGUI/</local>
+        <video>autotesting.ogv</video>
+        <screenshots>
+          <final>final-screenshot.png</final>
+          <montage>montage-of-video-frames.png</montage>
+        </screenshots>
+      </output>
+    </test>
+  </tests>
+</autotesting>

Added: trunk/mmaker/utils/autotesting2/tests/morphix-lightgui.xml
===================================================================
--- trunk/mmaker/utils/autotesting2/tests/morphix-lightgui.xml	                        (rev 0)
+++ trunk/mmaker/utils/autotesting2/tests/morphix-lightgui.xml	2009-07-01 19:24:57 UTC (rev 2661)
@@ -0,0 +1,32 @@
+<autotesting>
+  <tests>
+    <test>
+      <download>http://distro.ibiblio.org/pub/linux/distributions/morphix/autobuilds/iso/lightgui-basemod-2.6.23-2008-02-10_0018-latest.iso</download>
+      <title>Morphix LightGUI</title>
+      <description>Morphix. Daily build of LightGUI iso.</description>
+      <background>http://www.morphix.org/templates/MorphixORG/images/mambo_header.jpg</background>
+      <frequency>daily</frequency>
+      <qemu>
+        <xscreen>800x600x24</xscreen>
+        <binary>qemu</binary>
+        <options>-cdrom</options>
+        <!-- Time to pause before sending keys presses -->
+        <pause>10</pause>
+        <sendkeys>down,down,kp_enter</sendkeys>
+        <!-- Time to run qmeu -->
+        <time>600</time>
+      </qemu>
+      <output>
+        <!-- Number of test sets to keep -->
+        <keep>4</keep>
+        <root>/home/bms/autotesting/tests/</root>
+        <local>morphix/lightGUI/</local>
+        <video>autotesting.ogv</video>
+        <screenshots>
+          <final>final-screenshot.png</final>
+          <montage>montage-of-video-frames.png</montage>
+        </screenshots>
+      </output>
+    </test>
+  </tests>
+</autotesting>


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.

------------------------------------------------------------------------------