cl21: Add minimal required version functionality (#270)

This adds functionality to define minimal required version through the
ADD_TEST* macros. Tests that don't meet the version requirement will
be skipped.

By default the minimal required version is set to 1.0, subsequent
patches will set the appropriate version for each of the tests.

Signed-off-by: Radek Szymanski <radek.szymanski@arm.com>
diff --git a/test_common/harness/errorHelpers.h b/test_common/harness/errorHelpers.h
index 95b6636..e85a53f 100644
--- a/test_common/harness/errorHelpers.h
+++ b/test_common/harness/errorHelpers.h
@@ -16,6 +16,8 @@
 #ifndef _errorHelpers_h
 #define _errorHelpers_h
 
+#include <sstream>
+
 #ifdef __APPLE__
 #include <OpenCL/opencl.h>
 #else
@@ -97,6 +99,19 @@
 #define test_failure_warning_ret(errCode, expectedErrCode, msg, retValue) { if( errCode != expectedErrCode ) { print_failure_warning( errCode, expectedErrCode, msg ); warnings++ ; } }
 #define print_failure_warning(errCode, expectedErrCode, msg) log_error( "WARNING: %s! (Got %s, expected %s from %s:%d)\n", msg, IGetErrorString( errCode ), IGetErrorString( expectedErrCode ), __FILE__, __LINE__ );
 
+#define ASSERT_SUCCESS(expr, msg)                                                                  \
+    do                                                                                             \
+    {                                                                                              \
+        cl_int _temp_retval = (expr);                                                              \
+        if (_temp_retval != CL_SUCCESS)                                                            \
+        {                                                                                          \
+            std::stringstream ss;                                                                  \
+            ss << "ERROR: " << msg << "=" << IGetErrorString(_temp_retval)                         \
+               << " at " << __FILE__ << ":" << __LINE__ << "\n";                                   \
+            throw std::runtime_error(ss.str());                                                    \
+        }                                                                                          \
+    } while (0)
+
 extern const char    *IGetErrorString( int clErrorCode );
 
 extern float Ulp_Error_Half( cl_ushort test, float reference );
diff --git a/test_common/harness/testHarness.c b/test_common/harness/testHarness.c
index edb5eb9..ab029b3 100644
--- a/test_common/harness/testHarness.c
+++ b/test_common/harness/testHarness.c
@@ -17,6 +17,9 @@
 #include "compat.h"
 #include <stdio.h>
 #include <string.h>
+#include <cassert>
+#include <stdexcept>
+#include <vector>
 #include "threadTesting.h"
 #include "errorHelpers.h"
 #include "kernelHelpers.h"
@@ -703,11 +706,12 @@
     log_info( "%s...\n", test.name );
     fflush( stdout );
 
-    error = check_opencl_version_with_testname(test.name, deviceToUse);
-    if( error != CL_SUCCESS ) 
-    { 
-        print_missing_feature( error, test.name );
-        return TEST_SKIP; 
+    const Version device_version = get_device_cl_version(deviceToUse);
+    if (test.min_version > device_version)
+    {
+        log_info("%s skipped (requires at least version %s, but the device reports version %s)\n",
+                 test.name, test.min_version.to_string().c_str(), device_version.to_string().c_str());
+        return TEST_SKIP;
     }
 
     error = check_functions_for_offline_compiler(test.name, deviceToUse);
@@ -894,4 +898,28 @@
     return NULL;
 }
 
+Version get_device_cl_version(cl_device_id device)
+{
+    size_t str_size;
+    cl_int err = clGetDeviceInfo(device, CL_DEVICE_VERSION, 0, NULL, &str_size);
+    ASSERT_SUCCESS(err, "clGetDeviceInfo");
 
+    std::vector<char> str(str_size);
+    err = clGetDeviceInfo(device, CL_DEVICE_VERSION, str_size, str.data(), NULL);
+    ASSERT_SUCCESS(err, "clGetDeviceInfo");
+
+    if (strstr(str.data(), "OpenCL 1.0") != NULL)
+        return Version(1, 0);
+    else if (strstr(str.data(), "OpenCL 1.1") != NULL)
+        return Version(1, 1);
+    else if (strstr(str.data(), "OpenCL 1.2") != NULL)
+        return Version(1, 2);
+    else if (strstr(str.data(), "OpenCL 2.0") != NULL)
+        return Version(2, 0);
+    else if (strstr(str.data(), "OpenCL 2.1") != NULL)
+        return Version(2, 1);
+    else if (strstr(str.data(), "OpenCL 2.2") != NULL)
+        return Version(2, 2);
+
+    throw std::runtime_error(std::string("Unknown OpenCL version: ") + str.data());
+}
diff --git a/test_common/harness/testHarness.h b/test_common/harness/testHarness.h
index b988d67..9067d55 100644
--- a/test_common/harness/testHarness.h
+++ b/test_common/harness/testHarness.h
@@ -18,6 +18,8 @@
 
 #include "threadTesting.h"
 #include "clImageHelper.h"
+#include <string>
+#include <sstream>
 
 #include <string>
 
@@ -25,15 +27,36 @@
 extern "C" {
 #endif
 
-#define ADD_TEST(fn) {test_##fn, #fn}
-#define NOT_IMPLEMENTED_TEST(fn) {NULL, #fn}
+#define ADD_TEST(fn) {test_##fn, #fn, Version(1, 0)}
+#define ADD_TEST_VERSION(fn, ver) {test_##fn, #fn, ver}
+#define NOT_IMPLEMENTED_TEST(fn) {NULL, #fn, Version(0, 0)}
 
 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
 
+class Version
+{
+public:
+    Version() : m_major(0), m_minor(0) {}
+    Version(int major, int minor) : m_major(major), m_minor(minor) {}
+    bool operator>(const Version& rhs) const { return to_int() > rhs.to_int(); }
+    int to_int() const { return m_major * 10 + m_minor; }
+    std::string to_string() const 
+    {
+        std::stringstream ss;
+        ss << m_major << "." << m_minor;
+        return ss.str();
+    }
+
+private:
+    int m_major;
+    int m_minor;
+};
+
 typedef struct test_definition
 {
     basefn func;
     const char* name;
+    Version min_version;
 } test_definition;
 
 
@@ -99,6 +122,8 @@
 // is the only device available, the SAME device is returned, so check!
 extern cl_device_id GetOpposingDevice( cl_device_id device );
 
+Version get_device_cl_version(cl_device_id device);
+
 
 extern int      gFlushDenormsToZero;    // This is set to 1 if the device does not support denorms (CL_FP_DENORM)
 extern int      gInfNanSupport;         // This is set to 1 if the device supports infinities and NaNs
diff --git a/test_conformance/compatibility/test_common/harness/errorHelpers.h b/test_conformance/compatibility/test_common/harness/errorHelpers.h
index 9b5d709..54e73b0 100644
--- a/test_conformance/compatibility/test_common/harness/errorHelpers.h
+++ b/test_conformance/compatibility/test_common/harness/errorHelpers.h
@@ -16,6 +16,8 @@
 #ifndef _errorHelpers_h
 #define _errorHelpers_h
 
+#include <sstream>
+
 #ifdef __APPLE__
 #include <OpenCL/opencl.h>
 #else
@@ -85,6 +87,19 @@
 #define test_failure_warning_ret(errCode, expectedErrCode, msg, retValue) { if( errCode != expectedErrCode ) { print_failure_warning( errCode, expectedErrCode, msg ); warnings++ ; } }
 #define print_failure_warning(errCode, expectedErrCode, msg) log_error( "WARNING: %s! (Got %s, expected %s from %s:%d)\n", msg, IGetErrorString( errCode ), IGetErrorString( expectedErrCode ), __FILE__, __LINE__ );
 
+#define ASSERT_SUCCESS(expr, msg)                                                                  \
+    do                                                                                             \
+    {                                                                                              \
+        cl_int _temp_retval = (expr);                                                              \
+        if (_temp_retval != CL_SUCCESS)                                                            \
+        {                                                                                          \
+            std::stringstream ss;                                                                  \
+            ss << "ERROR: " << msg << "=" << IGetErrorString(_temp_retval)                         \
+               << " at " << __FILE__ << ":" << __LINE__ << "\n";                                   \
+            throw std::runtime_error(ss.str());                                                    \
+        }                                                                                          \
+    } while (0)
+
 extern const char    *IGetErrorString( int clErrorCode );
 
 extern float Ulp_Error_Half( cl_ushort test, float reference );
diff --git a/test_conformance/compatibility/test_common/harness/testHarness.c b/test_conformance/compatibility/test_common/harness/testHarness.c
index 2762dbe..95b57f0 100644
--- a/test_conformance/compatibility/test_common/harness/testHarness.c
+++ b/test_conformance/compatibility/test_common/harness/testHarness.c
@@ -23,6 +23,9 @@
 #endif
 
 #include <string.h>
+#include <cassert>
+#include <stdexcept>
+#include <vector>
 #include "threadTesting.h"
 #include "errorHelpers.h"
 #include "kernelHelpers.h"
@@ -701,6 +704,14 @@
     log_info( "%s...\n", test.name );
     fflush( stdout );
 
+    const Version device_version = get_device_cl_version(deviceToUse);
+    if (test.min_version > device_version)
+    {
+        log_info("%s skipped (requires at least version %s, but the device reports version %s)\n",
+                 test.name, test.min_version.to_string().c_str(), device_version.to_string().c_str());
+        return TEST_SKIP;
+    }
+
     if( test.func == NULL )
     {
         // Skip unimplemented test, can happen when all of the tests are selected
@@ -881,4 +892,28 @@
     return NULL;
 }
 
+Version get_device_cl_version(cl_device_id device)
+{
+    size_t str_size;
+    cl_int err = clGetDeviceInfo(device, CL_DEVICE_VERSION, 0, NULL, &str_size);
+    ASSERT_SUCCESS(err, "clGetDeviceInfo");
 
+    std::vector<char> str(str_size);
+    err = clGetDeviceInfo(device, CL_DEVICE_VERSION, str_size, str.data(), NULL);
+    ASSERT_SUCCESS(err, "clGetDeviceInfo");
+
+    if (strstr(str.data(), "OpenCL 1.0") != NULL)
+        return Version(1, 0);
+    else if (strstr(str.data(), "OpenCL 1.1") != NULL)
+        return Version(1, 1);
+    else if (strstr(str.data(), "OpenCL 1.2") != NULL)
+        return Version(1, 2);
+    else if (strstr(str.data(), "OpenCL 2.0") != NULL)
+        return Version(2, 0);
+    else if (strstr(str.data(), "OpenCL 2.1") != NULL)
+        return Version(2, 1);
+    else if (strstr(str.data(), "OpenCL 2.2") != NULL)
+        return Version(2, 2);
+
+    throw std::runtime_error(std::string("Unknown OpenCL version: ") + str.data());
+}
diff --git a/test_conformance/compatibility/test_common/harness/testHarness.h b/test_conformance/compatibility/test_common/harness/testHarness.h
index a3bf9f4..0a86d8d 100644
--- a/test_conformance/compatibility/test_common/harness/testHarness.h
+++ b/test_conformance/compatibility/test_common/harness/testHarness.h
@@ -18,20 +18,43 @@
 
 #include "threadTesting.h"
 #include "clImageHelper.h"
+#include <string>
+#include <sstream>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-#define ADD_TEST(fn) {test_##fn, #fn}
-#define NOT_IMPLEMENTED_TEST(fn) {NULL, #fn}
+#define ADD_TEST(fn) {test_##fn, #fn, Version(1, 0)}
+#define ADD_TEST_VERSION(fn, ver) {test_##fn, #fn, ver}
+#define NOT_IMPLEMENTED_TEST(fn) {NULL, #fn, Version(0, 0)}
 
 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
 
+class Version
+{
+public:
+    Version() : m_major(0), m_minor(0) {}
+    Version(int major, int minor) : m_major(major), m_minor(minor) {}
+    bool operator>(const Version& rhs) const { return to_int() > rhs.to_int(); }
+    int to_int() const { return m_major * 10 + m_minor; }
+    std::string to_string() const 
+    {
+        std::stringstream ss;
+        ss << m_major << "." << m_minor;
+        return ss.str();
+    }
+
+private:
+    int m_major;
+    int m_minor;
+};
+
 typedef struct test_definition
 {
     basefn func;
     const char* name;
+    Version min_version;
 } test_definition;
 
 typedef enum test_status
@@ -96,6 +119,8 @@
 // is the only device available, the SAME device is returned, so check!
 extern cl_device_id GetOpposingDevice( cl_device_id device );
 
+Version get_device_cl_version(cl_device_id device);
+
 
 extern int      gFlushDenormsToZero;    // This is set to 1 if the device does not support denorms (CL_FP_DENORM)
 extern int      gInfNanSupport;         // This is set to 1 if the device supports infinities and NaNs
diff --git a/test_conformance/spirv_new/main.cpp b/test_conformance/spirv_new/main.cpp
index a08bbe3..81e2dd1 100644
--- a/test_conformance/spirv_new/main.cpp
+++ b/test_conformance/spirv_new/main.cpp
@@ -79,6 +79,7 @@
     test_definition testDef;
     testDef.func = test->getFunction();
     testDef.name = testName;
+    testDef.min_version = Version(2, 1);
     testDefinitions.push_back(testDef);
 }