/*
* (C) 2017,2018 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include "tests.h"

#if defined(BOTAN_HAS_BIGINT)
   #include <botan/bigint.h>
#endif

namespace Botan_Tests {

/*
* Test the test framework :)
*/

class Test_Tests final : public Test
   {
   public:
      std::vector<Test::Result> run() override
         {
         /*
         Notation here is confusing. Idea is the result is the actual
         result of the test. The test_result is the result that is the
         testcase, and should typically be in a failure mode.
         */

         Test::Result result("Test Framework");

         // Test a few success corner cases first
         const std::string testcase_name = "Failing Test";

         result.test_throws("throws pi", []() { throw 3.14159; });

         // Test expected failure cases
            {
            Test::Result test_result(testcase_name);
            test_result.test_throws("doesn't throw", []() { });
            verify_failure("test_throws", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_failure("explicitly reported failure", std::vector<uint8_t>());
            verify_failure("explicit failure", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_failure("explicitly reported failure", "test error");
            verify_failure("explicit failure", result, test_result);
            }

            {
            verify_failure("explicit failure", result, Test::Result::Failure(testcase_name, "failure"));
            }

            {
            Test::Result test_result(testcase_name);
            std::vector<uint8_t> vec1(5), vec2(3, 9);
            test_result.test_eq("test vectors equal", vec1, vec2);
            verify_failure("test vectors equal", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            std::vector<uint8_t> vec1(5), vec2(5);
            test_result.test_ne("test vectors not equal", vec1, vec2);
            verify_failure("test vectors equal", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            std::vector<uint8_t> vec1(5), vec2(5);
            test_result.test_ne("test arrays not equal", vec1.data(), vec1.size(), vec2.data(), vec2.size());
            verify_failure("test vectors equal", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            size_t x = 5, y = 6;
            test_result.test_eq("test ints equal", x, y);
            verify_failure("test ints equal", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            size_t x = 5, y = 5;
            test_result.test_ne("test ints not equal", x, y);
            verify_failure("test ints not equal", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_is_nonempty("empty", "");
            verify_failure("test_is_nonempty", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_lt("not less", 5, 5);
            verify_failure("test_lt", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_lte("not lte", 6, 5);
            verify_failure("test_lte", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_gte("not gte", 5, 6);
            verify_failure("test_gte", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_ne("string ne", "foo", "foo");
            verify_failure("test_ne", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_rc_ok("test_func", -1);
            verify_failure("test_rc_ok", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_rc("test_func", 0, 5);
            verify_failure("test_rc", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_rc_fail("test_func", "testing", 0);
            verify_failure("test_rc_fail", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_throws("test_throws", "expected msg",
                                    []() { throw std::runtime_error("not the message"); });
            verify_failure("test_throws", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_throws("test_throws", "expected msg",
                                    []() { throw std::string("not even a std::exception"); });
            verify_failure("test_throws", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            test_result.test_throws("test_throws", "expected msg",
                                    []() { ; });
            verify_failure("test_throws", result, test_result);
            }

#if defined(BOTAN_HAS_BIGINT)
            {
            Test::Result test_result(testcase_name);
            Botan::BigInt x = 5, y = 6;
            test_result.test_eq("test ints equal", x, y);
            verify_failure("test ints equal", result, test_result);
            }

            {
            Test::Result test_result(testcase_name);
            Botan::BigInt x = 5, y = 5;
            test_result.test_ne("test ints not equal", x, y);
            verify_failure("test ints not equal", result, test_result);
            }
#endif

         return {result, test_testsuite_rng()};
         }

   private:
      Test::Result test_testsuite_rng()
         {
         Test::Result result("Testsuite_RNG");

         size_t histogram[256] = { 0 };

         const size_t RUNS = 1000;

         for(size_t i = 0; i != 256 * RUNS; ++i)
            {
            histogram[rng().next_byte()] += 1;
            }

         for(size_t i = 0; i != 256; ++i)
            {
            if(histogram[i] < RUNS / 2 || histogram[i] > RUNS * 2)
               result.test_failure("Testsuite_RNG produced non-uniform output");
            else
               result.test_success("Testsuite_RNG seemed roughly uniform");
            }

         return result;
         }

      void verify_failure(const std::string& what,
                          Test::Result& result,
                          const Test::Result& test_result)
         {
         if(test_result.tests_failed() > 0)
            {
            result.test_success("Got expected failure for " + what);
            const std::string result_str = test_result.result_string();

            result.confirm("result string contains FAIL",
                           result_str.find("FAIL") != std::string::npos);
            }
         else
            result.test_failure("Expected test to fail for " + what);
         }
   };

BOTAN_REGISTER_TEST("utils", "testcode", Test_Tests);

}
