tests: Make test framework class useful for XGL testing
authorCourtney Goeltzenleuchter <courtney@LunarG.com>
Thu, 4 Sep 2014 22:24:19 +0000 (16:24 -0600)
committerCourtney Goeltzenleuchter <courtney@LunarG.com>
Wed, 10 Sep 2014 22:44:58 +0000 (16:44 -0600)
Add the class XglTestClass that is intended to be used by
XGL test programs and knows how to capture interesting
test events such as images.
This includes the ability to parse two command line options
--show-images: bring up a simple image window that will show any
images of note (those that an application says to record). Results
are saved so that the operator can scroll through the images
at the completion of the test.
--save-images: Output the image as a ppm file using the test name
as the file base name.

tests/xgltestframework.cpp [new file with mode: 0644]
tests/xgltestframework.h [new file with mode: 0644]

diff --git a/tests/xgltestframework.cpp b/tests/xgltestframework.cpp
new file mode 100644 (file)
index 0000000..4ef5ccf
--- /dev/null
@@ -0,0 +1,336 @@
+//  XGL tests
+//
+//  Copyright (C) 2014 LunarG, Inc.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a
+//  copy of this software and associated documentation files (the "Software"),
+//  to deal in the Software without restriction, including without limitation
+//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
+//  and/or sell copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included
+//  in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+//  DEALINGS IN THE SOFTWARE.
+
+#include "xgltestframework.h"
+#include "GL/freeglut_std.h"
+
+XglTestFramework::XglTestFramework() :
+    m_glut_initialized( false )
+{
+}
+
+// Define all the static elements
+bool XglTestFramework::m_show_images = false;
+bool XglTestFramework::m_save_images = false;
+int XglTestFramework::m_width = 0;
+int XglTestFramework::m_height = 0;
+int XglTestFramework::m_window = 0;
+std::list<XglTestImageRecord> XglTestFramework::m_images;
+std::list<XglTestImageRecord>::iterator XglTestFramework::m_display_image;
+int m_display_image_idx = 0;
+
+void XglTestFramework::InitArgs(int *argc, char *argv[])
+{
+    int i, n;
+
+    for (i=0, n=0; i< *argc; i++) {
+        if (strncmp("--show-images", argv[i], 13) == 0) {
+            m_show_images = true;
+            continue;
+        }
+        if (strncmp("--save-images", argv[i], 13) == 0) {
+            m_save_images = true;
+            continue;
+        }
+
+        /*
+         * Since the above "consume" inputs, update argv
+         * so that it contains the trimmed list of args for glutInit
+         */
+        argv[n] = argv[i];
+        n++;
+    }
+
+    if (m_show_images) {
+        glutInit(argc, argv);
+    }
+}
+
+void XglTestFramework::Reshape( int w, int h )
+{
+    if (!m_show_images) return;  // Do nothing except save info if not enabled
+
+    // Resize window to be large enough to handle biggest image we've seen
+    // TODO: Probably need some sort of limits for the Window system.
+    if (w > m_width) {
+        m_width = w;
+    }
+    if (h > m_height) {
+        m_height = h;
+    }
+
+    glutReshapeWindow(m_width, m_height);
+
+    glViewport( 0, 0, m_width, m_height );
+    glMatrixMode( GL_PROJECTION );
+    glLoadIdentity();
+    glOrtho( 0.0, m_width, 0.0, m_height, 0.0, 2.0 );
+    glMatrixMode( GL_MODELVIEW );
+    glLoadIdentity();
+
+//    glScissor(width/4, height/4, width/2, height/2);
+}
+
+void XglTestFramework::WritePPM( const char *basename, XglImage *image )
+{
+    string filename;
+    XGL_RESULT err;
+    int x, y;
+
+    filename.append(basename);
+    filename.append(".ppm");
+
+    const XGL_IMAGE_SUBRESOURCE sr = {
+        XGL_IMAGE_ASPECT_COLOR, 0, 0
+    };
+    XGL_SUBRESOURCE_LAYOUT sr_layout;
+    XGL_UINT data_size;
+
+    err = xglGetImageSubresourceInfo( image->image(), &sr,
+                                      XGL_INFO_TYPE_SUBRESOURCE_LAYOUT,
+                                      &data_size, &sr_layout);
+    ASSERT_XGL_SUCCESS( err );
+    ASSERT_EQ(data_size, sizeof(sr_layout));
+
+    const char *ptr;
+
+    err = xglMapMemory( image->memory(), 0, (XGL_VOID **) &ptr );
+    ASSERT_XGL_SUCCESS( err );
+
+    ptr += sr_layout.offset;
+
+    ofstream file (filename.c_str());
+    ASSERT_TRUE(file.is_open()) << "Unable to open file: " << filename;
+
+    file << "P6\n";
+    file << image->width() << "\n";
+    file << image->height() << "\n";
+    file << 255 << "\n";
+
+    for (y = 0; y < image->height(); y++) {
+        const char *row = ptr;
+
+        for (x = 0; x < image->width(); x++) {
+            file.write(row, 3);
+            row += 4;
+        }
+
+        ptr += sr_layout.rowPitch;
+    }
+
+    file.close();
+
+    err = xglUnmapMemory( image->memory() );
+    ASSERT_XGL_SUCCESS( err );
+}
+
+void XglTestFramework::InitGLUT(int w, int h)
+{
+
+    if (!m_show_images) return;
+
+    if (!m_glut_initialized) {
+        glutInitWindowSize(w, h);
+
+        glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
+        m_window = glutCreateWindow(NULL);
+        m_glut_initialized = true;
+    }
+
+    Reshape(w, h);
+}
+
+void XglTestFramework::Show(const char *comment, XglImage *image)
+{
+    XGL_RESULT err;
+
+    const XGL_IMAGE_SUBRESOURCE sr = {
+        XGL_IMAGE_ASPECT_COLOR, 0, 0
+    };
+    XGL_SUBRESOURCE_LAYOUT sr_layout;
+    XGL_UINT data_size;
+
+    if (!m_show_images) return;
+
+    InitGLUT(image->width(), image->height());
+
+    err = xglGetImageSubresourceInfo( image->image(), &sr, XGL_INFO_TYPE_SUBRESOURCE_LAYOUT,
+                                      &data_size, &sr_layout);
+    ASSERT_XGL_SUCCESS( err );
+    ASSERT_EQ(data_size, sizeof(sr_layout));
+
+    const char *ptr;
+
+    err = image->MapMemory( (XGL_VOID **) &ptr );
+    ASSERT_XGL_SUCCESS( err );
+
+    ptr += sr_layout.offset;
+
+    XglTestImageRecord record;
+    record.m_title.append(comment);
+    record.m_width = image->width();
+    record.m_height = image->height();
+    // TODO: Need to make this more robust to handle different image formats
+    record.m_data_size = image->width()*image->height()*4;
+    record.m_data = malloc(record.m_data_size);
+    memcpy(record.m_data, ptr, record.m_data_size);
+    m_images.push_back(record);
+    m_display_image = --m_images.end();
+
+    Display();
+
+    err = image->UnmapMemory();
+    ASSERT_XGL_SUCCESS( err );
+}
+
+void XglTestFramework::RecordImage(XglImage *image)
+{
+    const ::testing::TestInfo* const test_info =
+      ::testing::UnitTest::GetInstance()->current_test_info();
+
+    if (m_save_images) {
+        WritePPM(test_info->test_case_name(), image);
+    }
+
+    if (m_show_images) {
+        Show(test_info->test_case_name(), image);
+    }
+}
+
+void XglTestFramework::Display()
+{
+    glutSetWindowTitle(m_display_image->m_title.c_str());
+
+    glClearColor(0, 0, 0, 0);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glRasterPos3f(0, 0, 0);
+    glBitmap(0, 0, 0, 0, 0, 0, NULL);
+    glDrawPixels(m_display_image->m_width, m_display_image->m_height,
+                 GL_RGBA, GL_UNSIGNED_BYTE, m_display_image->m_data);
+
+    glutSwapBuffers();
+}
+
+void XglTestFramework::Key( unsigned char key, int x, int y )
+{
+   (void) x;
+   (void) y;
+   switch (key) {
+      case 27:
+         glutDestroyWindow(m_window);
+         exit(0);
+         break;
+   }
+   glutPostRedisplay();
+}
+
+void XglTestFramework::SpecialKey( int key, int x, int y )
+{
+    (void) x;
+    (void) y;
+    switch (key) {
+    case GLUT_KEY_UP:
+    case GLUT_KEY_RIGHT:
+        ++m_display_image;
+        if (m_display_image == m_images.end()) {
+            m_display_image = m_images.begin();
+        }
+        break;
+    case GLUT_KEY_DOWN:
+    case GLUT_KEY_LEFT:
+        if (m_display_image == m_images.begin()) {
+            m_display_image = --m_images.end();
+        } else {
+            --m_display_image;
+        }
+
+        break;
+    }
+    glutPostRedisplay();
+}
+
+void XglTestFramework::Finish()
+{
+    if (m_images.size() == 0) return;
+
+    glutReshapeFunc( Reshape );
+    glutKeyboardFunc( Key );
+    glutSpecialFunc( SpecialKey );
+    glutDisplayFunc( Display );
+
+    glutMainLoop();
+}
+
+
+
+XglTestImageRecord::XglTestImageRecord() : // Constructor
+    m_width( 0 ),
+    m_height( 0 ),
+    m_data( NULL )
+{
+    m_data_size = 0;
+}
+
+XglTestImageRecord::~XglTestImageRecord()
+{
+
+}
+
+XglTestImageRecord::XglTestImageRecord(const XglTestImageRecord &copyin)   // Copy constructor to handle pass by value.
+{
+    m_title = copyin.m_title;
+    m_width = copyin.m_width;
+    m_height = copyin.m_height;
+    m_data_size = copyin.m_data_size;
+    m_data = copyin.m_data; // TODO: Do we need to copy the data or is pointer okay?
+}
+
+ostream &operator<<(ostream &output, const XglTestImageRecord &XglTestImageRecord)
+{
+    output << XglTestImageRecord.m_title << " (" << XglTestImageRecord.m_width <<
+              "," << XglTestImageRecord.m_height << ")" << endl;
+    return output;
+}
+
+XglTestImageRecord& XglTestImageRecord::operator=(const XglTestImageRecord &rhs)
+{
+    m_title = rhs.m_title;
+    m_width = rhs.m_width;
+    m_height = rhs.m_height;
+    m_data_size = rhs.m_data_size;
+    m_data = rhs.m_data;
+    return *this;
+}
+
+int XglTestImageRecord::operator==(const XglTestImageRecord &rhs) const
+{
+    if( this->m_data != rhs.m_data) return 0;
+    return 1;
+}
+
+// This function is required for built-in STL list functions like sort
+int XglTestImageRecord::operator<(const XglTestImageRecord &rhs) const
+{
+    if( this->m_data_size < rhs.m_data_size ) return 1;
+    return 0;
+}
+
diff --git a/tests/xgltestframework.h b/tests/xgltestframework.h
new file mode 100644 (file)
index 0000000..54d1990
--- /dev/null
@@ -0,0 +1,87 @@
+//  XGL tests
+//
+//  Copyright (C) 2014 LunarG, Inc.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a
+//  copy of this software and associated documentation files (the "Software"),
+//  to deal in the Software without restriction, including without limitation
+//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
+//  and/or sell copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included
+//  in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+//  DEALINGS IN THE SOFTWARE.
+
+#ifndef XGLTESTFRAMEWORK_H
+#define XGLTESTFRAMEWORK_H
+
+#include "gtest-1.7.0/include/gtest/gtest.h"
+#include "xglimage.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <iostream>
+#include <fstream>
+#include <list>
+using namespace std;
+
+class XglTestImageRecord
+{
+public:
+    XglTestImageRecord();
+    XglTestImageRecord(const XglTestImageRecord &);
+    ~XglTestImageRecord();
+    XglTestImageRecord &operator=(const XglTestImageRecord &rhs);
+    int operator==(const XglTestImageRecord &rhs) const;
+    int operator<(const XglTestImageRecord &rhs) const;
+
+    string      m_title;
+    void       *m_data;
+    unsigned    m_data_size;
+    int         m_width;
+    int         m_height;
+};
+
+class XglTestFramework : public ::testing::Test
+{
+public:
+    XglTestFramework();
+
+    static void InitArgs(int *argc, char *argv[]);
+    static void Finish();
+
+    void WritePPM( const char *basename, XglImage *image );
+    void Show(const char *comment, XglImage *image);
+    void RecordImage(XglImage *image);
+
+private:
+    static void Reshape( int w, int h );
+    static void Display();
+    static void Key(unsigned char key, int x, int y);
+    static void SpecialKey( int key, int x, int y );
+
+    void InitGLUT(int w, int h);
+
+    static bool         m_show_images;
+    static bool         m_save_images;
+    static std::list<XglTestImageRecord> m_images;
+    static std::list<XglTestImageRecord>::iterator m_display_image;
+    static int          m_display_image_idx;
+
+    bool                m_glut_initialized;
+    static int          m_window;
+    static int          m_width;            // Window width
+    static int          m_height;           // Window height
+};
+
+#endif // XGLTESTFRAMEWORK_H