Adding kvm test
The KVM team has been working on a set of tests for our virtualization
stack based on autotest. The project is known as kvm-autotest:
http://www.linux-kvm.org/page/KVM-Autotest
git://git.kernel.org/pub/scm/virt/kvm/kvm-autotest.git
In order to accomodate the needs of KVM Quality Engineering, quite a
substantial amount of code was written. A very brief overview of how the
tests are structured follows:
* kvm_runtest_2: Entry point, that defines hooks to all the KVM tests,
documented under:
http://www.linux-kvm.org/page/KVM-Autotest/Tests
* kvm_config: Module that handles the KVM configuration file format
http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File
It parses the configuration file and generates a list of dictionaries
that will be used by the other tests.
* step editor and stepmaker: A method to automate installation of guests
was devised, the idea is to send input to qemu through *step files*.
StepEditor and StepMaker are pygtk applications that allow to create and
edit step files.
From:
uril@redhat.com (Uri Lublin)
mgoldish@redhat.com (Michael Goldish)
dhuff@redhat.com (David Huff)
aeromenk@redhat.com (Alexey Eromenko)
mburns@redhat.com (Mike Burns)
Signed-off-by: Lucas Meneghel Rodrigues <lmr@redhat.com>
git-svn-id: http://test.kernel.org/svn/autotest/trunk@3187 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/tests/kvm/ppm_utils.py b/client/tests/kvm/ppm_utils.py
new file mode 100644
index 0000000..f0435ce
--- /dev/null
+++ b/client/tests/kvm/ppm_utils.py
@@ -0,0 +1,236 @@
+import md5, os, struct, time, re
+
+"""
+Utility functions to deal with ppm (qemu screendump format) files.
+
+@copyright: Red Hat 2008-2009
+"""
+
+# Some directory/filename utils, for consistency
+
+def find_id_for_screendump(md5sum, dir):
+ """
+ Search dir for a PPM file whose name ends with md5sum.
+
+ @param md5sum: md5 sum string
+ @param dir: Directory that holds the PPM files.
+ @return: The file's basename without any preceding path, e.g.
+ '20080101_120000_d41d8cd98f00b204e9800998ecf8427e.ppm'.
+ """
+ try:
+ files = os.listdir(dir)
+ except OSError:
+ files = []
+ for file in files:
+ exp = re.compile(r"(.*_)?" + md5sum + r"\.ppm", re.IGNORECASE)
+ if exp.match(file):
+ return file
+
+
+def generate_id_for_screendump(md5sum, dir):
+ """
+ Generate a unique filename using the given MD5 sum.
+
+ @return: Only the file basename, without any preceding path. The
+ filename consists of the current date and time, the MD5 sum and a .ppm
+ extension, e.g. '20080101_120000_d41d8cd98f00b204e9800998ecf8427e.ppm'.
+ """
+ filename = time.strftime("%Y%m%d_%H%M%S") + "_" + md5sum + ".ppm"
+ return filename
+
+
+def get_data_dir(steps_filename):
+ """
+ Return the data dir of the given steps filename.
+ """
+ filename = os.path.basename(steps_filename)
+ return os.path.join(os.path.dirname(steps_filename), "..", "steps_data",
+ filename + "_data")
+
+
+# Functions for working with PPM files
+
+def image_read_from_ppm_file(filename):
+ """
+ Read a PPM image.
+
+ @return: A 3 element tuple containing the width, height and data of the
+ image.
+ """
+ fin = open(filename,"rb")
+ l1 = fin.readline()
+ l2 = fin.readline()
+ l3 = fin.readline()
+ data = fin.read()
+ fin.close()
+
+ (w, h) = map(int, l2.split())
+ return (w, h, data)
+
+
+def image_write_to_ppm_file(filename, width, height, data):
+ """
+ Write a PPM image with the given width, height and data.
+
+ @param filename: PPM file path
+ @param width: PPM file width (pixels)
+ @param height: PPM file height (pixels)
+ """
+ fout = open(filename,"wb")
+ fout.write("P6\n")
+ fout.write("%d %d\n" % (width, height))
+ fout.write("255\n")
+ fout.write(data)
+ fout.close()
+
+
+def image_crop(width, height, data, x1, y1, dx, dy):
+ """
+ Crop an image.
+
+ @param width: Original image width
+ @param height: Original image height
+ @param data: Image data
+ @param x1: Desired x coordinate of the cropped region
+ @param y1: Desired y coordinate of the cropped region
+ @param dx: Desired width of the cropped region
+ @param dy: Desired height of the cropped region
+ @return: A 3-tuple containing the width, height and data of the
+ cropped image.
+ """
+ if x1 > width - 1: x1 = width - 1
+ if y1 > height - 1: y1 = height - 1
+ if dx > width - x1: dx = width - x1
+ if dy > height - y1: dy = height - y1
+ newdata = ""
+ index = (x1 + y1*width) * 3
+ for i in range(dy):
+ newdata += data[index:(index+dx*3)]
+ index += width*3
+ return (dx, dy, newdata)
+
+
+def image_md5sum(width, height, data):
+ """
+ Return the md5sum of an image.
+
+ @param width: PPM file width
+ @param height: PPM file height
+ @data: PPM file data
+ """
+ header = "P6\n%d %d\n255\n" % (width, height)
+ md5obj = md5.new(header)
+ md5obj.update(data)
+ return md5obj.hexdigest()
+
+
+def get_region_md5sum(width, height, data, x1, y1, dx, dy,
+ cropped_image_filename=None):
+ """
+ Return the md5sum of a cropped region.
+
+ @param width: Original image width
+ @param height: Original image height
+ @param data: Image data
+ @param x1: Desired x coord of the cropped region
+ @param y1: Desired y coord of the cropped region
+ @param dx: Desired width of the cropped region
+ @param dy: Desired height of the cropped region
+ @param cropped_image_filename: if not None, write the resulting cropped
+ image to a file with this name
+ """
+ (cw, ch, cdata) = image_crop(width, height, data, x1, y1, dx, dy)
+ # Write cropped image for debugging
+ if cropped_image_filename:
+ image_write_to_ppm_file(cropped_image_filename, cw, ch, cdata)
+ return image_md5sum(cw, ch, cdata)
+
+
+def image_verify_ppm_file(filename):
+ """
+ Verify the validity of a PPM file.
+
+ @param filename: Path of the file being verified.
+ @return: True if filename is a valid PPM image file. This function
+ reads only the first few bytes of the file so it should be rather fast.
+ """
+ try:
+ size = os.path.getsize(filename)
+ fin = open(filename, "rb")
+ assert(fin.readline().strip() == "P6")
+ (width, height) = map(int, fin.readline().split())
+ assert(width > 0 and height > 0)
+ assert(fin.readline().strip() == "255")
+ size_read = fin.tell()
+ fin.close()
+ assert(size - size_read == width*height*3)
+ return True
+ except:
+ return False
+
+
+def image_comparison(width, height, data1, data2):
+ """
+ Generate a green-red comparison image from two given images.
+
+ @param width: Width of both images
+ @param height: Height of both images
+ @param data1: Data of first image
+ @param data2: Data of second image
+ @return: A 3-element tuple containing the width, height and data of the
+ generated comparison image.
+
+ @note: Input images must be the same size.
+ """
+ newdata = ""
+ i = 0
+ while i < width*height*3:
+ # Compute monochromatic value of current pixel in data1
+ pixel1_str = data1[i:i+3]
+ temp = struct.unpack("BBB", pixel1_str)
+ value1 = int((temp[0] + temp[1] + temp[2]) / 3)
+ # Compute monochromatic value of current pixel in data2
+ pixel2_str = data2[i:i+3]
+ temp = struct.unpack("BBB", pixel2_str)
+ value2 = int((temp[0] + temp[1] + temp[2]) / 3)
+ # Compute average of the two values
+ value = int((value1 + value2) / 2)
+ # Scale value to the upper half of the range [0, 255]
+ value = 128 + value / 2
+ # Compare pixels
+ if pixel1_str == pixel2_str:
+ # Equal -- give the pixel a greenish hue
+ newpixel = [0, value, 0]
+ else:
+ # Not equal -- give the pixel a reddish hue
+ newpixel = [value, 0, 0]
+ newdata += struct.pack("BBB", newpixel[0], newpixel[1], newpixel[2])
+ i += 3
+ return (width, height, newdata)
+
+
+def image_fuzzy_compare(width, height, data1, data2):
+ """
+ Return the degree of equality of two given images.
+
+ @param width: Width of both images
+ @param height: Height of both images
+ @param data1: Data of first image
+ @param data2: Data of second image
+ @return: Ratio equal_pixel_count / total_pixel_count.
+
+ @note: Input images must be the same size.
+ """
+ equal = 0.0
+ different = 0.0
+ i = 0
+ while i < width*height*3:
+ pixel1_str = data1[i:i+3]
+ pixel2_str = data2[i:i+3]
+ # Compare pixels
+ if pixel1_str == pixel2_str:
+ equal += 1.0
+ else:
+ different += 1.0
+ i += 3
+ return equal / (equal + different)