[libgens/tests] TestSuite: Added color support on Windows.
authorDavid Korth <gerbilsoft@gerbilsoft.com>
Sat, 5 Sep 2015 02:11:12 +0000 (22:11 -0400)
committerDavid Korth <gerbilsoft@gerbilsoft.com>
Sat, 5 Sep 2015 02:11:12 +0000 (22:11 -0400)
Also check if color should be enabled on Linux.

Use a FILE* instead of hard-coding stderr.

The Print*() functions are no longer static, since they need
to access m_is_color and m_f_out.

src/libgens/tests/TestSuite.cpp
src/libgens/tests/TestSuite.hpp
src/libgens/tests/test_VdpPalette_DAC.cpp

index f93fd1d..6c2fe7f 100644 (file)
@@ -2,7 +2,7 @@
  * libgens/tests: Gens Emulation Library. (Test Suite)                     *
  * TestSuite.cpp: Test Suite base class.                                   *
  *                                                                         *
- * Copyright (c) 2011 by David Korth.                                      *
+ * Copyright (c) 2011-2015 by David Korth.                                 *
  *                                                                         *
  * 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   *
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
  ***************************************************************************/
 
+// DEPRECATED: All tests deriving from TestSuite should be
+// rewritten to use Google Test.
+
 #include "TestSuite.hpp"
 
 // C includes. (C++ namespace)
 #include <cstdio>
-using namespace std;
+#include <cstring>
 
 // C++ includes.
 #include <sstream>
 #include <string>
 #include <iomanip>
+using namespace std;
+
+#ifdef _WIN32
+#include <windows.h>
+#include <io.h>
+#define isatty(fileno) _isatty(fileno)
+#define fileno(stream) _fileno(stream)
+#else
+#include <unistd.h>
+#endif
 
 namespace LibGens {
 namespace Tests {
@@ -38,69 +51,115 @@ TestSuite::TestSuite()
        , m_tests_failed(0)
        , m_section_total(0)
        , m_section_failed(0)
-{ }
+       , m_is_color(false)
+       , m_f_out(NULL)
+{
+       // Initialize the output stream.
+       m_f_out = stderr;
 
+       // Check if the output stream supports color.
+       // (Based on Google Test.)
+       if (isatty(fileno(m_f_out))) {
+               // Output stream is a terminal.
+#ifdef _WIN32
+               // Always use color on Windows.
+               m_is_color = true;
+#else /* !_WIN32 */
+               // On Unix/Linux, check the terminal
+               // to see if it actually supports color.
+               const char *const term = getenv("TERM");
+               if (term) {
+                       if (!strcmp(term, "xterm") ||
+                           !strcmp(term, "xterm-color") ||
+                           !strcmp(term, "xterm-256color") ||
+                           !strcmp(term, "screen") ||
+                           !strcmp(term, "screen-256color") ||
+                           !strcmp(term, "linux") ||
+                           !strcmp(term, "cygwin"))
+                       {
+                               // Terminal supports color.
+                               m_is_color = true;
+                       }
+               }
+#endif /* _WIN32 */
+       }
+}
 
 /**
  * Start a new test section.
  */
 void TestSuite::newSection(void)
 {
-       if (m_section_total > 0)
-       {
-               fprintf(stderr, "Section complete. %d/%d tests passed.\n\n",
+       if (m_section_total > 0) {
+               fprintf(m_f_out, "Section complete. %d/%d tests passed.\n\n",
                        (m_section_total - m_section_failed), m_section_total);
        }
-       
+
        // Reset the section counters.
        m_section_failed = 0;
        m_section_total = 0;
 }
 
-
 /**
  * Print the test results and indicate the tests are completed.
  */
 void TestSuite::testsCompleted(void)
 {
        newSection();
-       fprintf(stderr, "Tests complete. %d/%d tests passed.\n",
+       fprintf(m_f_out, "Tests complete. %d/%d tests passed.\n",
                (m_tests_total - m_tests_failed), m_tests_total);
 }
 
 
 // Colorization functions.
+// TODO: Switch to fputs()?
 #ifndef _WIN32
 // ANSI escape sequences.
-#define ANSI_ESC_COLOR(x)      "\x1B[01;3" #x "m"
-#define ANSI_ESC_END           "\x1B[00m"
-void TestSuite::PrintFail(FILE *f)
-       { fprintf(f, "[" ANSI_ESC_COLOR(1) "FAIL" ANSI_ESC_END "] "); }
-void TestSuite::PrintWarn(FILE *f)
-       { fprintf(f, "[" ANSI_ESC_COLOR(3) "WARN" ANSI_ESC_END "] "); }
-void TestSuite::PrintPass(FILE *f)
-       { fprintf(f, "[" ANSI_ESC_COLOR(2) "PASS" ANSI_ESC_END "] "); }
-void TestSuite::PrintInfo(FILE *f)
-       { fprintf(f, "[" ANSI_ESC_COLOR(5) "INFO" ANSI_ESC_END "] "); }
-void TestSuite::PrintUnknown(FILE *f)
-       { fprintf(f, "[" ANSI_ESC_COLOR(3) "UNKNOWN" ANSI_ESC_END "] "); }
-#undef ANSI_ESC_COLOR
-#undef ANSI_ESC_END
+#define PrintMsg(fn, str, color) \
+void TestSuite::Print##fn(void) \
+{ \
+       if (m_is_color) { \
+               fprintf(m_f_out, "[\x1B[01;3" #color "m" str "\x1B[00m" "] "); \
+       } else { \
+               fprintf(m_f_out, "[" str "] "); \
+       } \
+}
+PrintMsg(Fail, "FAIL", 1)
+PrintMsg(Warn, "WARN", 3)
+PrintMsg(Pass, "PASS", 2)
+PrintMsg(Info, "INFO", 5)
+PrintMsg(Unknown, "UNKNOWN", 3)
 #else /* _WIN32 */
-// TODO: Add Win32 support.
-void TestSuite::PrintFail(FILE *f)
-       { fprintf(f, "[FAIL] "); }
-void TestSuite::PrintWarn(FILE *f)
-       { fprintf(f, "[WARN] "); }
-void TestSuite::PrintPass(FILE *f)
-       { fprintf(f, "[PASS] "); }
-void TestSuite::PrintInfo(FILE *f)
-       { fprintf(f, "[INFO] "); }
-void TestSuite::PrintUnknown(FILE *f)
-       { fprintf(f, "[UNKNOWN] "); }
+#define PrintMsg(fn, str, color) \
+void TestSuite::Print##fn(void) \
+{ \
+       if (m_is_color) { \
+               fprintf(m_f_out, "["); \
+               const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); \
+               /* Get the current text color. */ \
+               CONSOLE_SCREEN_BUFFER_INFO buffer_info; \
+               GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); \
+               const WORD old_color_attrs = buffer_info.wAttributes; \
+               /* Flush the stream buffers to prevent issues when changing attributes. */ \
+               fflush(stdout); \
+               fflush(stderr); \
+               SetConsoleTextAttribute(stdout_handle, color | FOREGROUND_INTENSITY); \
+               fprintf(m_f_out, "%s", str); \
+               fflush(m_f_out); \
+               SetConsoleTextAttribute(stdout_handle, old_color_attrs); \
+               fprintf(m_f_out, "] "); \
+       } else { \
+               fprintf(m_f_out, "[" str "] "); \
+       } \
+}
+// NOTE: RGB bits are inverted compared to ANSI terminals.
+PrintMsg(Fail, "FAIL", FOREGROUND_RED)
+PrintMsg(Warn, "WARN", FOREGROUND_RED | FOREGROUND_GREEN)
+PrintMsg(Pass, "PASS", FOREGROUND_GREEN)
+PrintMsg(Info, "INFO", FOREGROUND_RED | FOREGROUND_BLUE)
+PrintMsg(Unknown, "UNKNOWN", FOREGROUND_RED | FOREGROUND_GREEN)
 #endif /* _WIN32 */
 
-
 /**
  * Internal function to indicate a test passed.
  * @param expr Stringified expression, or NULL if nothing should be printed.
@@ -109,15 +168,13 @@ void TestSuite::assertPass(const char *expr)
 {
        m_tests_total++;
        m_section_total++;
-       
-       if (expr)
-       {
-               PrintPass(stderr);
-               fprintf(stderr, "Test `%s' passed.\n", expr);
+
+       if (expr) {
+               PrintPass();
+               fprintf(m_f_out, "Test `%s' passed.\n", expr);
        }
 }
 
-
 /**
  * Internal function to indicate a test failed.
  * @param expr Stringified expression, or NULL if nothing should be printed.
@@ -128,15 +185,13 @@ void TestSuite::assertFail(const char *expr)
        m_tests_failed++;
        m_section_total++;
        m_section_failed++;
-       
-       if (expr)
-       {
-               PrintFail(stderr);
-               fprintf(stderr, "Test `%s' failed.\n", expr);
+
+       if (expr) {
+               PrintFail();
+               fprintf(m_f_out, "Test `%s' failed.\n", expr);
        }
 }
 
-
 /**
  * Check two hex values for equality.
  * @param test Test name.
@@ -149,29 +204,28 @@ bool TestSuite::assertEquals_hex(const char *test, T expected, T actual)
 {
        m_tests_total++;
        m_section_total++;
-       
-       if (expected == actual)
-               PrintPass(stderr);
-       else
-       {
+
+       if (expected == actual) {
+               PrintPass();
+       } else {
                m_tests_failed++;
                m_section_failed++;
-               PrintFail(stderr);
+               PrintFail();
        }
-       
+
        // Convert the expected value to a string.
        stringstream ss;
        ss << std::hex << std::uppercase << std::setfill('0') << std::setw(sizeof(T)*2);
        ss << expected;
        string exp_str = ss.str();
-       
+
        // Convert the actual value to a string.
        ss.str("");
        ss << std::hex << std::uppercase << std::setfill('0') << std::setw(sizeof(T)*2);
        ss << actual;
        string act_str = ss.str();
-       
-       fprintf(stderr, "%s: expected %s, got %s\n", test, exp_str.c_str(), act_str.c_str());
+
+       fprintf(m_f_out, "%s: expected %s, got %s\n", test, exp_str.c_str(), act_str.c_str());
        return (expected == actual);
 }
 
@@ -201,12 +255,12 @@ bool TestSuite::assertEquals(const char *test, T expected, T actual)
        m_section_total++;
        
        if (expected == actual)
-               PrintPass(stderr);
+               PrintPass();
        else
        {
                m_tests_failed++;
                m_section_failed++;
-               PrintFail(stderr);
+               PrintFail();
        }
        
        // Convert the expected value to a string.
@@ -219,7 +273,7 @@ bool TestSuite::assertEquals(const char *test, T expected, T actual)
        ss << actual;
        string act_str = ss.str();
        
-       fprintf(stderr, "%s: expected %s, got %s\n", test, exp_str.c_str(), act_str.c_str());
+       fprintf(m_f_out, "%s: expected %s, got %s\n", test, exp_str.c_str(), act_str.c_str());
        return (expected == actual);
 }
 
index 15d5d78..6feca0a 100644 (file)
@@ -2,7 +2,7 @@
  * libgens/tests: Gens Emulation Library. (Test Suite)                     *
  * TestSuite.hpp: Test Suite base class.                                   *
  *                                                                         *
- * Copyright (c) 2011 by David Korth.                                      *
+ * Copyright (c) 2011-2015 by David Korth.                                 *
  *                                                                         *
  * 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   *
@@ -19,6 +19,9 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.           *
  ***************************************************************************/
 
+// DEPRECATED: All tests deriving from TestSuite should be
+// rewritten to use Google Test.
+
 #ifndef __LIBGENS_TESTS_TESTSUITE_HPP__
 #define __LIBGENS_TESTS_TESTSUITE_HPP__
 
@@ -45,57 +48,57 @@ class TestSuite
 {
        public:
                TestSuite();
-               
+
                /**
                 * Execute the test suite.
                 * @return 0 on success; negative on fatal error; positive if tests failed.
                 */
                virtual int exec(void) = 0;
-               
+
                /**
                 * Get the number of passed tests.
                 */
                int testsPassed(void) const;
-               
+
                /**
                 * Get the number of failed tests.
                 */
                int testsFailed(void) const;
-               
+
                /**
                 * Get the total number of tests.
                 */
                int testsTotal(void) const;
-       
+
        protected:
                /**
                 * Start a new test section.
                 */
                void newSection(void);
-               
+
                /**
                 * Print the test results and indicate the tests are completed.
                 */
                void testsCompleted(void);
-               
-               static void PrintFail(FILE *f);
-               static void PrintWarn(FILE *f);
-               static void PrintPass(FILE *f);
-               static void PrintInfo(FILE *f);
-               static void PrintUnknown(FILE *f);
-               
+
+               void PrintFail(void);
+               void PrintWarn(void);
+               void PrintPass(void);
+               void PrintInfo(void);
+               void PrintUnknown(void);
+
                /**
                 * Internal function to indicate a test passed.
                 * @param expr Stringified expression, or NULL if nothing should be printed.
                 */
                void assertPass(const char *expr);
-               
+
                /**
                 * Internal function to indicate a test failed.
                 * @param expr Stringified expression, or NULL if nothing should be printed.
                 */
                void assertFail(const char *expr);
-               
+
                /**
                 * Check two hex values for equality.
                 * @param test Test name.
@@ -104,7 +107,7 @@ class TestSuite
                 */
                template<typename T>
                bool assertEquals_hex(const char *test, T expected, T actual);
-               
+
                /**
                 * Check two values for equality.
                 * @param test Test name.
@@ -113,17 +116,20 @@ class TestSuite
                 */
                template<typename T>
                bool assertEquals(const char *test, T expected, T actual);
-       
+
        private:
                /** All tests. **/
-               
                int m_tests_total;      // Total number of tests executed.
                int m_tests_failed;     // Total number of tests failed.
-               
+
                /** Test section. **/
-               
                int m_section_total;    // Number of tests executed in this section.
                int m_section_failed;   // Number of tests failed in this section.
+
+               /** Output stream. **/
+               bool m_is_color;        // Does m_f_out support color?
+       protected:
+               FILE *m_f_out;          // Accessible by subclasses.
 };
 
 /**
index df2c1bc..789dfc2 100644 (file)
@@ -114,12 +114,12 @@ void Test_VdpPalette_DAC::assertCRam(int lineNum, int bpp, uint16_t cram, uint32
 
        // Assertion failed.
        assertFail(NULL);
-       PrintFail(stderr);
+       PrintFail();
        if (bpp <= 16) {
-               fprintf(stderr, "Line %d, rgb%d: [%04X] expected %04X, got %04X\n",
+               fprintf(m_f_out, "Line %d, rgb%d: [%04X] expected %04X, got %04X\n",
                        lineNum, bpp, cram, expected, actual);
        } else {
-               fprintf(stderr, "Line %d, rgb%d: [%04X] expected %06X, got %06X\n",
+               fprintf(m_f_out, "Line %d, rgb%d: [%04X] expected %06X, got %06X\n",
                        lineNum, bpp, cram, expected, actual);
        }
 }
@@ -130,12 +130,12 @@ void Test_VdpPalette_DAC::assertCRam(int lineNum, int bpp, uint16_t cram, uint32
  */
 int Test_VdpPalette_DAC::exec(void)
 {
-       fprintf(stderr, "LibGens: VdpPalette DAC test suite.\n\n");
+       fprintf(m_f_out, "LibGens: VdpPalette DAC test suite.\n\n");
 
        // Open the file.
        FILE *f = fopen(m_filename.c_str(), "r");
        if (!f) {
-               fprintf(stderr, "Error opening '%s': %s\n",
+               fprintf(m_f_out, "Error opening '%s': %s\n",
                        m_filename.c_str(), strerror(errno));
                return -1;
        }
@@ -206,20 +206,20 @@ int Test_VdpPalette_DAC::exec(void)
                        token = strtok(NULL, ":");
                        int header_version;
                        if (parse_number(token, 16, &header_version) != 0) {
-                               PrintFail(stderr);
-                               fprintf(stderr, "Invalid file header.\n");
+                               PrintFail();
+                               fprintf(m_f_out, "Invalid file header.\n");
                                goto fail;
                        }
 
                        if (header_version != PALTEST_VERSION) {
-                               PrintFail(stderr);
-                               fprintf(stderr, "Incorrect PalTest version. (expected %04X; found %04X) [FATAL]\n",
+                               PrintFail();
+                               fprintf(m_f_out, "Incorrect PalTest version. (expected %04X; found %04X) [FATAL]\n",
                                        header_version, PALTEST_VERSION);
                                goto fail;
                        }
 
                        // Header is valid.
-                       fprintf(stderr, "Processing file: '%s'\n\n",
+                       fprintf(m_f_out, "Processing file: '%s'\n\n",
                                m_filename.c_str());
                        isInit = true;
                } else if (!strcasecmp(token, PALTEST_CMD_PALMODE)) {
@@ -245,8 +245,8 @@ int Test_VdpPalette_DAC::exec(void)
                                palMode = VdpPalette::PALMODE_TMS9918;
                        */
                        else {
-                               PrintFail(stderr);
-                               fprintf(stderr, "Unsupported PalMode: '%s'\n", token);
+                               PrintFail();
+                               fprintf(m_f_out, "Unsupported PalMode: '%s'\n", token);
                                goto fail;
                        }
 
@@ -280,7 +280,7 @@ int Test_VdpPalette_DAC::exec(void)
                        }
 
                        // Print the selected palette mode.
-                       fprintf(stderr, "Selected PalMode: '%s'\n", palMode_str);
+                       fprintf(m_f_out, "Selected PalMode: '%s'\n", palMode_str);
 
                        // Set the palette mode.
                        vdp15->setPalMode(palMode);
@@ -306,8 +306,8 @@ int Test_VdpPalette_DAC::exec(void)
                        else if (!strcasecmp(token, PALTEST_SHMODE_HIGHLIGHT))
                                shMode = 0x80;
                        else {
-                               PrintFail(stderr);
-                               fprintf(stderr, "Unsupported SHMode: '%s'\n", token);
+                               PrintFail();
+                               fprintf(m_f_out, "Unsupported SHMode: '%s'\n", token);
                                goto fail;
                        }
 
@@ -326,11 +326,11 @@ int Test_VdpPalette_DAC::exec(void)
                        }
 
                        // Print the selected Shadow/Highlight mode.
-                       fprintf(stderr, "Selected SHMode: '%s'\n", shMode_str);
+                       fprintf(m_f_out, "Selected SHMode: '%s'\n", shMode_str);
                        if (palMode != VdpPalette::PALMODE_MD) {
                                // ColorScale is only supported with MD palettes.
-                               PrintWarn(stderr);
-                               fprintf(stderr, "SHMode has no effect with PalMode '%s'.\n", palMode_str);
+                               PrintWarn();
+                               fprintf(m_f_out, "SHMode has no effect with PalMode '%s'.\n", palMode_str);
                        }
 
                        // Set the Shadow/Highlight mode.
@@ -354,32 +354,32 @@ int Test_VdpPalette_DAC::exec(void)
                        token = strtok(NULL, ":");
                        if (!token || parse_number(token, 16, &cram) != 0) {
                                assertFail(NULL);
-                               PrintFail(stderr);
-                               fprintf(stderr, "Line %d, ColorEntry: Invalid CRAM field: '%s'.\n",
+                               PrintFail();
+                               fprintf(m_f_out, "Line %d, ColorEntry: Invalid CRAM field: '%s'.\n",
                                        lineNum, (token ? token : "(null)"));
                                continue;
                        }
                        token = strtok(NULL, ":");
                        if (!token || parse_number(token, 16, &rgb15) != 0) {
                                assertFail(NULL);
-                               PrintFail(stderr);
-                               fprintf(stderr, "Line %d, ColorEntry: Invalid RGB15 field: '%s'.\n",
+                               PrintFail();
+                               fprintf(m_f_out, "Line %d, ColorEntry: Invalid RGB15 field: '%s'.\n",
                                        lineNum, (token ? token : "(null)"));
                                continue;
                        }
                        token = strtok(NULL, ":");
                        if (!token || parse_number(token, 16, &rgb16) != 0) {
                                assertFail(NULL);
-                               PrintFail(stderr);
-                               fprintf(stderr, "Line %d, ColorEntry: Invalid RGB16 field: '%s'.\n",
+                               PrintFail();
+                               fprintf(m_f_out, "Line %d, ColorEntry: Invalid RGB16 field: '%s'.\n",
                                        lineNum, (token ? token : "(null)"));
                                continue;
                        }
                        token = strtok(NULL, ":");
                        if (!token || parse_number(token, 16, &rgb32) != 0) {
                                assertFail(NULL);
-                               PrintFail(stderr);
-                               fprintf(stderr, "Line %d, ColorEntry: Invalid RGB32 field: '%s'.\n",
+                               PrintFail();
+                               fprintf(m_f_out, "Line %d, ColorEntry: Invalid RGB32 field: '%s'.\n",
                                        lineNum, (token ? token : "(null)"));
                                continue;
                        }
@@ -425,8 +425,8 @@ int Test_VdpPalette_DAC::exec(void)
        return testsFailed();
 
 no_magic:
-       PrintFail(stderr);      // TODO: [FATAL] instead of [FAIL].
-       fprintf(stderr, "'%s': PalTest version line is missing.\n", m_filename.c_str());
+       PrintFail();    // TODO: [FATAL] instead of [FAIL].
+       fprintf(m_f_out, "'%s': PalTest version line is missing.\n", m_filename.c_str());
 fail:
        fclose(f);
 
@@ -441,13 +441,12 @@ fail:
 
 int main(int argc, char *argv[])
 {
-       if (argc < 2)
-       {
+       if (argc < 2) {
                // No filename was specified.
                fprintf(stderr, "Specify a filename to test.\n");
                return EXIT_FAILURE;
        }
-       
+
        LibGens::Tests::Test_VdpPalette_DAC vdpTest(argv[1]);
        int ret = vdpTest.exec();
        return ((ret == 0) ? ret : vdpTest.testsFailed());