Browse code

Add implementation

Robert Cranston authored on 15/03/2021 19:32:43
Showing 6 changed files

... ...
@@ -9,3 +9,21 @@ project(gltest
9 9
     VERSION 1.0.0
10 10
     LANGUAGES CXX
11 11
 )
12
+
13
+## Main target
14
+add_library(${PROJECT_NAME})
15
+
16
+## Common
17
+include(common.cmake)
18
+common(
19
+    CXX_STANDARD 11
20
+    FETCHCONTENT
21
+        https://git.rcrnstn.net/rcrnstn/glbase
22
+        https://git.rcrnstn.net/rcrnstn/glbackend
23
+    DEPENDENCIES_PRIVATE
24
+        glbase
25
+        glbackend
26
+    DEPENDENCIES_TESTS
27
+        glbase
28
+        glbackend
29
+)
... ...
@@ -6,7 +6,110 @@ A [C++11][]/[OpenGL][] \>=[1.0][] [testing][] library.
6 6
 [C++11]: https://en.wikipedia.org/wiki/C++11
7 7
 [OpenGL]: https://en.wikipedia.org/wiki/OpenGL
8 8
 [1.0]: https://en.wikipedia.org/wiki/OpenGL#Version_history
9
-[testing]: https://en.wikipedia.org/wiki/Software_testing
9
+
10
+## Usage
11
+
12
+```cpp
13
+#include <cerrno>
14
+#include <cstring>
15
+#include <fstream>
16
+#include <stdexcept>
17
+#include <string>
18
+
19
+#include <GL/glew.h>
20
+
21
+#include <glbackend.hpp>
22
+#include <gltest.hpp>
23
+
24
+
25
+// Function that throws with a system-supplied message.
26
+void file_open(std::string const & filename)
27
+{
28
+    auto file = std::ifstream{"nonexistent"};
29
+    if (!file)
30
+        throw std::runtime_error(std::string{} +
31
+            "Could not open file '" + filename + "':\n" +
32
+            std::strerror(errno) + "." // NOLINT
33
+        );
34
+}
35
+
36
+
37
+GLTEST(gltest, 640, 480, 2, 0)
38
+{
39
+    backend.prefix("assets/tests");
40
+
41
+    GLTEST_EXPECT_VALUE(
42
+        GL_FALSE,
43
+        glIsEnabled(GL_DEPTH_TEST)
44
+    )
45
+    GLTEST_EXPECT_EXCEPTION(false,
46
+        "Expected exception",
47
+        throw std::runtime_error("Expected exception")
48
+    )
49
+    GLTEST_EXPECT_EXCEPTION(true,
50
+        "Could not open file 'nonexistent':\n",
51
+        // "No such file or directory"
52
+        file_open("nonexistent")
53
+    )
54
+
55
+    constexpr auto size = 0.5;
56
+    glBegin(GL_TRIANGLE_STRIP);
57
+    glVertex3f(-size, -size, 0);
58
+    glVertex3f(-size, +size, 0);
59
+    glVertex3f(+size, -size, 0);
60
+    glVertex3f(+size, +size, 0);
61
+    glEnd();
62
+    backend.tga_compare("GLTest.tga");
63
+}
64
+```
65
+
66
+A `main()` entry point is provided which calls each test declared with the
67
+macro `GLTEST(VERSION_MAJOR, VERSION_MINOR, WIDTH, HEIGHT, NAME)`. A double
68
+buffered OpenGL debug context compatible with the specified version and with a
69
+window of the specified width and height is set up prior to each test run and
70
+teared down afterwards.
71
+
72
+If OpenGL \>=4.3 or the extension [`KHR_debug`][] is available,
73
+non-notification debug messages throw errors unless disabled by calling `bool
74
+gltest_debug(false)` (which returns the old value). The root directory for
75
+reading and writing files is set with `void gltest_root(std::string)`. Timing
76
+can be manipulated with `void gltest_set_time(float)` and `float
77
+gltest_get_time()`. Swapping buffers is performed with `void
78
+gltest_swap_buffers()`.
79
+
80
+The macro `GLTEST_EXPECT_VALUE(EXPR, VALUE)` runs `EXPR`, compares the result
81
+to `VALUE` and throws an error if they do not match.
82
+
83
+The macro `GLTEST_EXPECT_EXCEPTION(PREFIX, EXPR, WHAT)` catches any
84
+[`std::exception`][]-derived exception thrown by `EXPR`, compares its
85
+[`what()`][] to `WHAT` and throws an error if they do not match. If `PREFIX` is
86
+true it is only required that `WHAT` is a prefix of `what()`, to allow trailing
87
+external messages of unknown format. An error is also thrown if `EXPR` throws
88
+no exception at all. `gltest_debug(false)` is automatically called before
89
+`EXPR` is run, the previous value is restored afterwards.
90
+
91
+The macro `GLTEST_EXPECT_FRAME(PATH)` reads `PATH` from disk, compares it to
92
+the current (back) framebuffer and throws an error if they do not match. If
93
+`PATH` does not exist, the current framebuffer is instead written to it and a
94
+warning is printed.
95
+
96
+If a `GLTEST_EXPECT_*` fails, the values of `GL_VENDOR`, `GL_RENDERER`,
97
+`GL_VERSION` and `GL_SHADING_LANGUAGE_VERSION` is printed to standard error. In
98
+addition, `GLTEST_EXPECT_VALUE` or `GLTEST_EXPECT_EXCEPTION` print something in
99
+the style of:
100
+
101
+```
102
+>>> Expression:
103
+glIsEnabled(GL_DEPTH_TEST)
104
+>>> Expected value:
105
+GL_TRUE = 1
106
+>>> Got:
107
+0
108
+```
109
+
110
+[`std::exception`]: https://en.cppreference.com/w/cpp/error/exception
111
+[`what()`]: https://en.cppreference.com/w/cpp/error/exception/what
112
+[`KHR_debug`]: https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_debug.txt
10 113
 
11 114
 ## Build system
12 115
 
13 116
new file mode 100644
14 117
Binary files /dev/null and b/assets/tests/GLTest.tga differ
... ...
@@ -0,0 +1,109 @@
1
+/// Guards
2
+
3
+#ifndef GLTEST_HPP_
4
+#define GLTEST_HPP_
5
+
6
+
7
+/// Includes
8
+
9
+#include <string>
10
+#include <vector>
11
+
12
+// #include <glbackend.hpp>
13
+class GLBackend;
14
+
15
+
16
+/// Struct definition
17
+
18
+struct GLTest_
19
+{
20
+    std::string   title;
21
+    int           width;
22
+    int           height;
23
+    int           version_major;
24
+    int           version_minor;
25
+    void        (*func)(GLBackend & backend);
26
+};
27
+
28
+
29
+/// Function forward declarations
30
+
31
+std::vector<GLTest_> & gltests_();
32
+
33
+
34
+/// Macros
35
+
36
+#define GLTEST(TITLE, WIDTH, HEIGHT, VERSION_MAJOR, VERSION_MINOR) \
37
+    void static gltest_func_##TITLE(GLBackend & backend); \
38
+    auto static gltest_init_##TITLE = ( \
39
+        gltests_().push_back({ \
40
+            #TITLE, \
41
+            WIDTH, \
42
+            HEIGHT, \
43
+            VERSION_MAJOR, \
44
+            VERSION_MINOR, \
45
+            gltest_func_##TITLE \
46
+        }), \
47
+        0 \
48
+    ); \
49
+    void static gltest_func_##TITLE(GLBackend & backend)
50
+
51
+
52
+#define GLTEST_EXPECT_VALUE(VALUE, ...) \
53
+    { \
54
+        auto gltest_value_      = VALUE; \
55
+        auto gltest_expr_value_ = __VA_ARGS__; \
56
+        auto gltest_error_      = std::string{} + \
57
+            ">>> Expression:\n" + \
58
+            #__VA_ARGS__ + "\n" + \
59
+            ">>> Expected value:\n" + \
60
+            #VALUE + " = " + std::to_string(gltest_value_) + "\n" + \
61
+            ">>> Got:"; \
62
+        if (gltest_expr_value_ != gltest_value_) \
63
+            throw std::runtime_error{std::string{} + \
64
+                gltest_error_ + "\n" + \
65
+                std::to_string(gltest_expr_value_) \
66
+            }; \
67
+    }
68
+
69
+
70
+#define GLTEST_EXPECT_EXCEPTION(PREFIX, WHAT, ...) \
71
+    { \
72
+        auto gltest_prefix_    = PREFIX; \
73
+        auto gltest_expr_what_ = std::string{}; \
74
+        auto gltest_what_      = WHAT; \
75
+        try \
76
+        { \
77
+            __VA_ARGS__; \
78
+        } \
79
+        catch (std::exception const & gltest_exception_) \
80
+        { \
81
+            gltest_expr_what_ = gltest_exception_.what(); \
82
+        } \
83
+        auto gltest_error_ = std::string{} + \
84
+            ">>> Expression:\n" + \
85
+            #__VA_ARGS__ + "\n" + \
86
+            ">>> Expected exception" + \
87
+                (gltest_prefix_ ? " prefix" : "") + ":\n" + \
88
+            gltest_what_ + "\n" + \
89
+            ">>> Got:"; \
90
+        if (gltest_expr_what_.empty()) \
91
+            throw std::runtime_error{std::string{} + \
92
+                gltest_error_ + " none." \
93
+            }; \
94
+        if (gltest_prefix_ \
95
+            ? (gltest_expr_what_.rfind(gltest_what_, 0) \
96
+                == gltest_expr_what_.npos \
97
+            ) \
98
+            : (gltest_expr_what_ != gltest_what_) \
99
+        ) \
100
+            throw std::runtime_error{std::string{} + \
101
+                gltest_error_ + "\n" + \
102
+                gltest_expr_what_ \
103
+            }; \
104
+    }
105
+
106
+
107
+/// Guards
108
+
109
+#endif
... ...
@@ -0,0 +1,35 @@
1
+#include <gltest.hpp>
2
+
3
+#include <stdexcept>
4
+#include <string>
5
+
6
+#include <glbackend_default.hpp>
7
+
8
+
9
+std::vector<GLTest_> & gltests_()
10
+{
11
+    auto static tests = std::vector<GLTest_>{};
12
+    return tests;
13
+}
14
+
15
+
16
+int main()
17
+{
18
+    for (auto const & test : gltests_())
19
+    {
20
+        auto error = std::string{};
21
+        auto backend = GLBackendDefault(
22
+            test.title,
23
+            {test.width, test.height},
24
+            {test.version_major, test.version_minor}
25
+        );
26
+        backend.debug_callback([&](std::string const & message)
27
+        {
28
+            if (error.empty())
29
+                error = message;
30
+        });
31
+        test.func(backend);
32
+        if (!error.empty())
33
+            throw std::runtime_error{error};
34
+    }
35
+}
0 36
new file mode 100644
... ...
@@ -0,0 +1,51 @@
1
+#include <cerrno>
2
+#include <cstring>
3
+#include <fstream>
4
+#include <stdexcept>
5
+#include <string>
6
+
7
+#include <GL/glew.h>
8
+
9
+#include <glbackend.hpp>
10
+#include <gltest.hpp>
11
+
12
+
13
+// Function that throws with a system-supplied message.
14
+void file_open(std::string const & filename)
15
+{
16
+    auto file = std::ifstream{"nonexistent"};
17
+    if (!file)
18
+        throw std::runtime_error(std::string{} +
19
+            "Could not open file '" + filename + "':\n" +
20
+            std::strerror(errno) + "." // NOLINT
21
+        );
22
+}
23
+
24
+
25
+GLTEST(gltest, 640, 480, 2, 0)
26
+{
27
+    backend.prefix("assets/tests");
28
+
29
+    GLTEST_EXPECT_VALUE(
30
+        GL_FALSE,
31
+        glIsEnabled(GL_DEPTH_TEST)
32
+    )
33
+    GLTEST_EXPECT_EXCEPTION(false,
34
+        "Expected exception",
35
+        throw std::runtime_error("Expected exception")
36
+    )
37
+    GLTEST_EXPECT_EXCEPTION(true,
38
+        "Could not open file 'nonexistent':\n",
39
+        // "No such file or directory"
40
+        file_open("nonexistent")
41
+    )
42
+
43
+    constexpr auto size = 0.5;
44
+    glBegin(GL_TRIANGLE_STRIP);
45
+    glVertex3f(-size, -size, 0);
46
+    glVertex3f(-size, +size, 0);
47
+    glVertex3f(+size, -size, 0);
48
+    glVertex3f(+size, +size, 0);
49
+    glEnd();
50
+    backend.tga_compare("GLTest.tga");
51
+}