text: /*-------------------------------------------------------------------------
   * drawElements Internal Test Module
   * ---------------------------------
   *
   * Copyright 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   *//*!
   * \file
   * \brief Miscellaneous framework tests.
   *//*--------------------------------------------------------------------*/
  
  #include "ditFrameworkTests.hpp"
  #include "ditTextureFormatTests.hpp"
  #include "ditAstcTests.hpp"
  #include "ditVulkanTests.hpp"
  
  #include "tcuFloatFormat.hpp"
  #include "tcuEither.hpp"
  #include "tcuTestLog.hpp"
  #include "tcuCommandLine.hpp"
  
  #include "rrRenderer.hpp"
  #include "tcuTextureUtil.hpp"
  #include "tcuVectorUtil.hpp"
  #include "tcuFloat.hpp"
  
  #include "deRandom.hpp"
  #include "deArrayUtil.hpp"
  
  namespace dit
  {
  
  namespace
  {
  
  using std::string;
  using std::vector;
  using tcu::TestLog;
  
  struct MatchCase
  {
  	enum Expected { NO_MATCH, MATCH_GROUP, MATCH_CASE, EXPECTED_LAST };
  
  	const char*	path;
  	Expected	expected;
  };
  
  const char* getMatchCaseExpectedDesc (MatchCase::Expected expected)
  {
  	static const char* descs[] =
  	{
  		"no match",
  		"group to match",
  		"case to match"
  	};
  	return de::getSizedArrayElement<MatchCase::EXPECTED_LAST>(descs, expected);
  }
  
  class CaseListParserCase : public tcu::TestCase
  {
  public:
  	CaseListParserCase (tcu::TestContext& testCtx, const char* name, const char* caseList, const MatchCase* subCases, int numSubCases)
  		: tcu::TestCase	(testCtx, name, "")
  		, m_caseList	(caseList)
  		, m_subCases	(subCases)
  		, m_numSubCases	(numSubCases)
  	{
  	}
  
  	IterateResult iterate (void)
  	{
  		TestLog&			log		= m_testCtx.getLog();
  		tcu::CommandLine	cmdLine;
  		int					numPass	= 0;
  
  		log << TestLog::Message << "Input:\n\"" << m_caseList << "\"" << TestLog::EndMessage;
  
  		{
  			const char* argv[] =
  			{
  				"deqp",
  				"--deqp-caselist",
  				m_caseList
  			};
  
  			if (!cmdLine.parse(DE_LENGTH_OF_ARRAY(argv), argv))
  				TCU_FAIL("Failed to parse case list");
  		}
  
  		for (int subCaseNdx = 0; subCaseNdx < m_numSubCases; subCaseNdx++)
  		{
  			const MatchCase&	curCase		= m_subCases[subCaseNdx];
  			bool				matchGroup;
  			bool				matchCase;
  
  			log << TestLog::Message << "Checking \"" << curCase.path << "\""
  									<< ", expecting " << getMatchCaseExpectedDesc(curCase.expected)
  				<< TestLog::EndMessage;
  
  			matchGroup	= cmdLine.checkTestGroupName(curCase.path);
  			matchCase	= cmdLine.checkTestCaseName(curCase.path);
  
  			if ((matchGroup	== (curCase.expected == MatchCase::MATCH_GROUP)) &&
  				(matchCase	== (curCase.expected == MatchCase::MATCH_CASE)))
  			{
  				log << TestLog::Message << "   pass" << TestLog::EndMessage;
  				numPass += 1;
  			}
  			else
  				log << TestLog::Message << "   FAIL!" << TestLog::EndMessage;
  		}
  
  		m_testCtx.setTestResult((numPass == m_numSubCases) ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
  								(numPass == m_numSubCases) ? "All passed"			: "Unexpected match result");
  
  		return STOP;
  	}
  
  private:
  	const char* const			m_caseList;
  	const MatchCase* const		m_subCases;
  	const int					m_numSubCases;
  };
  
  class NegativeCaseListCase : public tcu::TestCase
  {
  public:
  	NegativeCaseListCase (tcu::TestContext& testCtx, const char* name, const char* caseList)
  		: tcu::TestCase	(testCtx, name, "")
  		, m_caseList	(caseList)
  	{
  	}
  
  	IterateResult iterate (void)
  	{
  		TestLog&			log		= m_testCtx.getLog();
  		tcu::CommandLine	cmdLine;
  
  		log << TestLog::Message << "Input:\n\"" << m_caseList << "\"" << TestLog::EndMessage;
  
  		{
  			const char* argv[] =
  			{
  				"deqp",
  				"--deqp-caselist",
  				m_caseList
  			};
  
  			if (cmdLine.parse(DE_LENGTH_OF_ARRAY(argv), argv))
  				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Parsing passed, should have failed");
  			else
  				m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Parsing failed as expected");
  		}
  
  		return STOP;
  	}
  
  private:
  	const char* const	m_caseList;
  };
  
  class TrieParserTests : public tcu::TestCaseGroup
  {
  public:
  	TrieParserTests (tcu::TestContext& testCtx)
  		: tcu::TestCaseGroup(testCtx, "trie", "Test case trie parser tests")
  	{
  	}
  
  	void init (void)
  	{
  		{
  			static const char* const	caseList	= "{test}";
  			static const MatchCase		subCases[]	=
  			{
  				{ "test",		MatchCase::MATCH_CASE	},
  				{ "test.cd",	MatchCase::NO_MATCH		},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "single_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{a{b}}";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "simple_group_1", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{a{b,c}}";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "simple_group_2", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{a{b},c{d,e}}";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.c",	MatchCase::NO_MATCH		},
  				{ "a.d",	MatchCase::NO_MATCH		},
  				{ "a.e",	MatchCase::NO_MATCH		},
  				{ "c",		MatchCase::MATCH_GROUP	},
  				{ "c.b",	MatchCase::NO_MATCH		},
  				{ "c.d",	MatchCase::MATCH_CASE	},
  				{ "c.e",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "two_groups", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{a,c{d,e}}";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_CASE	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::NO_MATCH		},
  				{ "a.d",	MatchCase::NO_MATCH		},
  				{ "a.e",	MatchCase::NO_MATCH		},
  				{ "c",		MatchCase::MATCH_GROUP	},
  				{ "c.b",	MatchCase::NO_MATCH		},
  				{ "c.d",	MatchCase::MATCH_CASE	},
  				{ "c.e",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "case_group", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{c{d,e},a}";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_CASE	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::NO_MATCH		},
  				{ "a.d",	MatchCase::NO_MATCH		},
  				{ "a.e",	MatchCase::NO_MATCH		},
  				{ "c",		MatchCase::MATCH_GROUP	},
  				{ "c.b",	MatchCase::NO_MATCH		},
  				{ "c.d",	MatchCase::MATCH_CASE	},
  				{ "c.e",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "group_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{test}\r";
  			static const MatchCase		subCases[]	=
  			{
  				{ "test",		MatchCase::MATCH_CASE	},
  				{ "test.cd",	MatchCase::NO_MATCH		},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "trailing_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{test}\n";
  			static const MatchCase		subCases[]	=
  			{
  				{ "test",		MatchCase::MATCH_CASE	},
  				{ "test.cd",	MatchCase::NO_MATCH		},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "trailing_lf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "{test}\r\n";
  			static const MatchCase		subCases[]	=
  			{
  				{ "test",		MatchCase::MATCH_CASE	},
  				{ "test.cd",	MatchCase::NO_MATCH		},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "trailing_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  
  		// Negative tests
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_string",			""));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_line",				"\n"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_root",				"{}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_group",				"{test{}}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name_1",		"{{}}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name_2",		"{{test}}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_1",		"{"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_2",		"{test"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_3",		"{test,"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_4",		"{test{a}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_root_5",		"{a,b"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_1",	"{test{"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_2",	"{test{a"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_3",	"{test{a,"));
  		addChild(new NegativeCaseListCase(m_testCtx, "unterminated_group_4",	"{test{a,b"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_1",		"{a,,b}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_2",		"{,b}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name_3",		"{a,}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "no_separator",			"{a{b}c}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "invalid_char_1",			"{a.b}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "invalid_char_2",			"{a[]}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "trailing_char_1",			"{a}}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "trailing_char_2",			"{a}x"));
  		addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_1",		"{\na}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_2",		"{a\n,b}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_3",		"{a,\nb}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_4",		"{a{b\n}}"));
  		addChild(new NegativeCaseListCase(m_testCtx, "embedded_newline_5",		"{a{b}\n}"));
  	}
  };
  
  class ListParserTests : public tcu::TestCaseGroup
  {
  public:
  	ListParserTests (tcu::TestContext& testCtx)
  		: tcu::TestCaseGroup(testCtx, "list", "Test case list parser tests")
  	{
  	}
  
  	void init (void)
  	{
  		{
  			static const char* const	caseList	= "test";
  			static const MatchCase		subCases[]	=
  			{
  				{ "test",		MatchCase::MATCH_CASE	},
  				{ "test.cd",	MatchCase::NO_MATCH		},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "single_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "simple_group_1", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\na.c";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "simple_group_2", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\na.c";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "separator_ln", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\ra.c";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "separator_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\r\na.c";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "separator_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\na.c\n";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "end_ln", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\na.c\r";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "end_cr", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\na.c\r\n";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.a",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "end_crlf", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b\nc.d\nc.e";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_GROUP	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::MATCH_CASE	},
  				{ "a.c",	MatchCase::NO_MATCH		},
  				{ "a.d",	MatchCase::NO_MATCH		},
  				{ "a.e",	MatchCase::NO_MATCH		},
  				{ "c",		MatchCase::MATCH_GROUP	},
  				{ "c.b",	MatchCase::NO_MATCH		},
  				{ "c.d",	MatchCase::MATCH_CASE	},
  				{ "c.e",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "two_groups", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a\nc.d\nc.e";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_CASE	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::NO_MATCH		},
  				{ "a.d",	MatchCase::NO_MATCH		},
  				{ "a.e",	MatchCase::NO_MATCH		},
  				{ "c",		MatchCase::MATCH_GROUP	},
  				{ "c.b",	MatchCase::NO_MATCH		},
  				{ "c.d",	MatchCase::MATCH_CASE	},
  				{ "c.e",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "case_group", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "c.d\nc.e\na";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",		MatchCase::MATCH_CASE	},
  				{ "b",		MatchCase::NO_MATCH		},
  				{ "a.b",	MatchCase::NO_MATCH		},
  				{ "a.c",	MatchCase::NO_MATCH		},
  				{ "a.d",	MatchCase::NO_MATCH		},
  				{ "a.e",	MatchCase::NO_MATCH		},
  				{ "c",		MatchCase::MATCH_GROUP	},
  				{ "c.b",	MatchCase::NO_MATCH		},
  				{ "c.d",	MatchCase::MATCH_CASE	},
  				{ "c.e",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "group_case", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	= "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.x";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",												MatchCase::MATCH_GROUP	},
  				{ "b",												MatchCase::NO_MATCH		},
  				{ "a.b",											MatchCase::MATCH_GROUP	},
  				{ "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.x",	MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "long_name", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	=
  				"a.b.c.d.e\n"
  				"a.b.c.f\n"
  				"x.y.z\n"
  				"a.b.c.d.g\n"
  				"a.b.c.x\n";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",				MatchCase::MATCH_GROUP	},
  				{ "a.b",			MatchCase::MATCH_GROUP	},
  				{ "a.b.c.d.e",		MatchCase::MATCH_CASE	},
  				{ "a.b.c.d.g",		MatchCase::MATCH_CASE	},
  				{ "x.y",			MatchCase::MATCH_GROUP	},
  				{ "x.y.z",			MatchCase::MATCH_CASE	},
  				{ "a.b.c.f",		MatchCase::MATCH_CASE	},
  				{ "a.b.c.x",		MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "partial_prefix", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  		{
  			static const char* const	caseList	=
  				"a.a.c.d\n"
  				"a.b.c.d\n";
  			static const MatchCase		subCases[]	=
  			{
  				{ "a",				MatchCase::MATCH_GROUP	},
  				{ "a.a",			MatchCase::MATCH_GROUP	},
  				{ "a.b.c.d",		MatchCase::MATCH_CASE	},
  				{ "a.b.c.d",		MatchCase::MATCH_CASE	},
  			};
  			addChild(new CaseListParserCase(m_testCtx, "reparenting", caseList, subCases, DE_LENGTH_OF_ARRAY(subCases)));
  		}
  
  		// Negative tests
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_string",			""));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_line",				"\n"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_group_name",		".test"));
  		addChild(new NegativeCaseListCase(m_testCtx, "empty_case_name",			"test."));
  	}
  };
  
  class CaseListParserTests : public tcu::TestCaseGroup
  {
  public:
  	CaseListParserTests (tcu::TestContext& testCtx)
  		: tcu::TestCaseGroup(testCtx, "case_list_parser", "Test case list parser tests")
  	{
  	}
  
  	void init (void)
  	{
  		addChild(new TrieParserTests(m_testCtx));
  		addChild(new ListParserTests(m_testCtx));
  	}
  };
  
  inline deUint32 ulpDiff (float a, float b)
  {
  	const deUint32 ab = tcu::Float32(a).bits();
  	const deUint32 bb = tcu::Float32(b).bits();
  	return de::max(ab, bb) - de::min(ab, bb);
  }
  
  template<int Size>
  inline tcu::Vector<deUint32, Size> ulpDiff (const tcu::Vector<float, Size>& a, const tcu::Vector<float,  Size>& b)
  {
  	tcu::Vector<deUint32, Size> res;
  	for (int ndx = 0; ndx < Size; ndx++)
  		res[ndx] = ulpDiff(a[ndx], b[ndx]);
  	return res;
  }
  
  class ConstantInterpolationTest : public tcu::TestCase
  {
  public:
  	ConstantInterpolationTest (tcu::TestContext& testCtx)
  		: tcu::TestCase(testCtx, "const_interpolation", "Constant value interpolation")
  	{
  		const int supportedMsaaLevels[] = {1, 2, 4, 8, 16};
  
  		for (int msaaNdx = 0; msaaNdx < DE_LENGTH_OF_ARRAY(supportedMsaaLevels); msaaNdx++)
  		{
  			const int numSamples = supportedMsaaLevels[msaaNdx];
  			{
  				SubCase c;
  				c.rtSize	= tcu::IVec3(128, 128, numSamples);
  				c.vtx[0]	= tcu::Vec4(-1.0f, -1.0f, 0.5f, 1.0f);
  				c.vtx[1]	= tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
  				c.vtx[2]	= tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
  				c.varying	= tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
  				m_cases.push_back(c);
  			}
  
  			{
  				SubCase c;
  				c.rtSize	= tcu::IVec3(128, 128, numSamples);
  				c.vtx[0]	= tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
  				c.vtx[1]	= tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
  				c.vtx[2]	= tcu::Vec4(+1.0f, +1.0f, 0.5f, 1.0f);
  				c.varying	= tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
  				m_cases.push_back(c);
  			}
  			{
  				SubCase c;
  				c.rtSize	= tcu::IVec3(129, 113, numSamples);
  				c.vtx[0]	= tcu::Vec4(-1.0f, -1.0f, 0.5f, 1.0f);
  				c.vtx[1]	= tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
  				c.vtx[2]	= tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
  				c.varying	= tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
  				m_cases.push_back(c);
  			}
  			{
  				SubCase c;
  				c.rtSize	= tcu::IVec3(107, 131, numSamples);
  				c.vtx[0]	= tcu::Vec4(-1.0f, +1.0f, 0.5f, 1.0f);
  				c.vtx[1]	= tcu::Vec4(+1.0f, -1.0f, 0.5f, 1.0f);
  				c.vtx[2]	= tcu::Vec4(+1.0f, +1.0f, 0.5f, 1.0f);
  				c.varying	= tcu::Vec4(0.0f, 1.0f, 8.0f, -8.0f);
  				m_cases.push_back(c);
  			}
  		}
  
  		{
  			de::Random rnd(0x89423f);
  			for (int ndx = 0; ndx < 25; ndx++)
  			{
  				const float	depth	= rnd.getFloat()*2.0f - 1.0f;
  				SubCase		c;
  
  				c.rtSize.x() = rnd.getInt(16, 256);
  				c.rtSize.y() = rnd.getInt(16, 256);
  				c.rtSize.z() = rnd.choose<int>(DE_ARRAY_BEGIN(supportedMsaaLevels), DE_ARRAY_END(supportedMsaaLevels));
  
  				for (int vtxNdx = 0; vtxNdx < DE_LENGTH_OF_ARRAY(c.vtx); vtxNdx++)
  				{
  					c.vtx[vtxNdx].x() = rnd.getFloat()*2.0f - 1.0f;
  					c.vtx[vtxNdx].y() = rnd.getFloat()*2.0f - 1.0f;
  					c.vtx[vtxNdx].z() = depth;
  					c.vtx[vtxNdx].w() = 1.0f;
  				}
  
  				for (int compNdx = 0; compNdx < 4; compNdx++)
  				{
  					float v;
  					do
  					{
  						v = tcu::Float32(rnd.getUint32()).asFloat();
  					} while (deFloatIsInf(v) || deFloatIsNaN(v));
  					c.varying[compNdx] = v;
  				}
  				m_cases.push_back(c);
  			}
  		}
  	}
  
  	void init (void)
  	{
  		m_caseIter = m_cases.begin();
  		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "All iterations passed");
  	}
  
  	IterateResult iterate (void)
  	{
  		{
  			tcu::ScopedLogSection section(m_testCtx.getLog(), "SubCase", "");
  			runCase(*m_caseIter);
  		}
  		return (++m_caseIter != m_cases.end()) ? CONTINUE : STOP;
  	}
  
  protected:
  	struct SubCase
  	{
  		tcu::IVec3	rtSize;	// (width, height, samples)
  		tcu::Vec4	vtx[3];
  		tcu::Vec4	varying;
  	};
  
  	void runCase (const SubCase& subCase)
  	{
  		using namespace tcu;
  
  		const deUint32	maxColorUlpDiff	= 2;
  		const deUint32	maxDepthUlpDiff	= 0;
  
  		const int		width			= subCase.rtSize.x();
  		const int		height			= subCase.rtSize.y();
  		const int		numSamples		= subCase.rtSize.z();
  		const float		zn				= 0.0f;
  		const float		zf				= 1.0f;
  
  		TextureLevel	interpolated	(TextureFormat(TextureFormat::RGBA, TextureFormat::FLOAT), numSamples, width, height);
  		TextureLevel	depthStencil	(TextureFormat(TextureFormat::DS, TextureFormat::FLOAT_UNSIGNED_INT_24_8_REV), numSamples, width, height);
  
  		m_testCtx.getLog() << TestLog::Message
  						   << "RT size (w, h, #samples) = " << subCase.rtSize << "\n"
  						   << "vtx[0] = " << subCase.vtx[0] << "\n"
  						   << "vtx[1] = " << subCase.vtx[1] << "\n"
  						   << "vtx[2] = " << subCase.vtx[2] << "\n"
  						   << "color = " << subCase.varying
  						   << TestLog::EndMessage;
  
  		clear			(interpolated.getAccess(), subCase.varying - Vec4(0.0f, 0.0f, 0.0f, 1.0f));
  		clearDepth		(depthStencil.getAccess(), 0.0f);
  		clearStencil	(depthStencil.getAccess(), 0);
  
  		{
  			class VtxShader : public rr::VertexShader
  			{
  			public:
  				VtxShader (void)
  					: rr::VertexShader(2, 1)
  				{
  					m_inputs[0].type	= rr::GENERICVECTYPE_FLOAT;
  					m_inputs[1].type	= rr::GENERICVECTYPE_FLOAT;
  					m_outputs[0].type	= rr::GENERICVECTYPE_FLOAT;
  				}
  
  				void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
  				{
  					for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
  					{
  						rr::readVertexAttrib(packets[packetNdx]->position, inputs[0], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
  						packets[packetNdx]->outputs[0] = rr::readVertexAttribFloat(inputs[1], packets[packetNdx]->instanceNdx, packets[packetNdx]->vertexNdx);
  					}
  				}
  			} vtxShader;
  
  			class FragShader : public rr::FragmentShader
  			{
  			public:
  				FragShader (void)
  					: rr::FragmentShader(1, 1)
  				{
  					m_inputs[0].type	= rr::GENERICVECTYPE_FLOAT;
  					m_outputs[0].type	= rr::GENERICVECTYPE_FLOAT;
  				}
  
  				void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
  				{
  					for (int packetNdx = 0; packetNdx < numPackets; packetNdx++)
  					{
  						for (int fragNdx = 0; fragNdx < rr::NUM_FRAGMENTS_PER_PACKET; fragNdx++)
  						{
  							const tcu::Vec4 interp = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
  							rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, interp);
  						}
  					}
  				}
  			} fragShader;
  
  			const rr::Program						program			(&vtxShader, &fragShader);
  
  			const rr::MultisamplePixelBufferAccess	colorAccess		= rr::MultisamplePixelBufferAccess::fromMultisampleAccess(interpolated.getAccess());
  			const rr::MultisamplePixelBufferAccess	dsAccess		= rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthStencil.getAccess());
  			const rr::RenderTarget					renderTarget	(colorAccess, dsAccess, dsAccess);
  			const rr::VertexAttrib					vertexAttribs[]	=
  			{
  				rr::VertexAttrib(rr::VERTEXATTRIBTYPE_FLOAT, 4, 0, 0, subCase.vtx),
  				rr::VertexAttrib(subCase.varying)
  			};
  			rr::ViewportState						viewport		(colorAccess);
  			rr::RenderState							state			(viewport);
  			const rr::DrawCommand					drawCmd			(state, renderTarget, program, DE_LENGTH_OF_ARRAY(vertexAttribs), vertexAttribs, rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, 3, 0));
  			const rr::Renderer						renderer;
  
  			viewport.zn	= zn;
  			viewport.zf	= zf;
  
  			state.fragOps.depthTestEnabled							= true;
  			state.fragOps.depthFunc									= rr::TESTFUNC_ALWAYS;
  			state.fragOps.stencilTestEnabled						= true;
  			state.fragOps.stencilStates[rr::FACETYPE_BACK].func		= rr::TESTFUNC_ALWAYS;
  			state.fragOps.stencilStates[rr::FACETYPE_BACK].dpPass	= rr::STENCILOP_INCR;
  			state.fragOps.stencilStates[rr::FACETYPE_FRONT]			= state.fragOps.stencilStates[rr::FACETYPE_BACK];
  
  			renderer.draw(drawCmd);
  		}
  
  		// Verify interpolated values
  		{
  			TextureLevel					resolvedColor			(interpolated.getFormat(), width, height); // For debugging
  			TextureLevel					resolvedDepthStencil	(depthStencil.getFormat(), width, height); // For debugging
  			TextureLevel					errorMask				(TextureFormat(TextureFormat::RGB, TextureFormat::UNORM_INT8), width, height);
  			const ConstPixelBufferAccess	interpAccess			= interpolated.getAccess();
  			const ConstPixelBufferAccess	dsAccess				= depthStencil.getAccess();
  			const PixelBufferAccess			errorAccess				= errorMask.getAccess();
  			int								numCoveredSamples		= 0;
  			int								numFailedColorSamples	= 0;
  			int								numFailedDepthSamples	= 0;
  			const bool						verifyDepth				= (subCase.vtx[0].z() == subCase.vtx[1].z()) &&
  																	  (subCase.vtx[1].z() == subCase.vtx[2].z());
  			const float						refDepth				= subCase.vtx[0].z()*(zf - zn)/2.0f + (zn + zf)/2.0f;
  
  			rr::resolveMultisampleBuffer(resolvedColor.getAccess(), rr::MultisampleConstPixelBufferAccess::fromMultisampleAccess(interpolated.getAccess()));
  			rr::resolveMultisampleBuffer(resolvedDepthStencil.getAccess(), rr::MultisampleConstPixelBufferAccess::fromMultisampleAccess(depthStencil.getAccess()));
  			clear(errorAccess, Vec4(0.0f, 1.0f, 0.0f, 1.0f));
  
  			for (int y = 0; y < height; y++)
  			{
  				for (int x = 0; x < width; x++)
  				{
  					for (int sampleNdx = 0; sampleNdx < numSamples; sampleNdx++)
  					{
  						if (dsAccess.getPixStencil(sampleNdx, x, y) != 0)
  						{
  							const Vec4		color		= interpAccess.getPixel(sampleNdx, x, y);
  							const UVec4		colorDiff	= ulpDiff(color, subCase.varying);
  							const bool		colorOk		= boolAll(lessThanEqual(colorDiff, tcu::UVec4(maxColorUlpDiff)));
  
  							const float		depth		= dsAccess.getPixDepth(sampleNdx, x, y);
  							const deUint32	depthDiff	= ulpDiff(depth, refDepth);
  							const bool		depthOk		= verifyDepth && (depthDiff <= maxDepthUlpDiff);
  
  							const int		maxMsgs		= 10;
  
  							numCoveredSamples += 1;
  
  							if (!colorOk)
  							{
  								numFailedColorSamples += 1;
  
  								if (numFailedColorSamples <= maxMsgs)
  									m_testCtx.getLog() << TestLog::Message
  													   << "FAIL: " << tcu::IVec3(x, y, sampleNdx)
  													   << " color ulp diff = " << colorDiff
  													   << TestLog::EndMessage;
  							}
  
  							if (!depthOk)
  								numFailedDepthSamples += 1;
  
  							if (!colorOk || !depthOk)
  								errorAccess.setPixel(errorAccess.getPixel(x, y) + Vec4(1.0f, -1.0f, 0.0f, 0.0f) / float(numSamples-1), x, y);
  						}
  					}
  				}
  			}
  
  			m_testCtx.getLog() << TestLog::Image("ResolvedColor", "Resolved colorbuffer", resolvedColor)
  							   << TestLog::Image("ResolvedDepthStencil", "Resolved depth- & stencilbuffer", resolvedDepthStencil);
  
  			if (numFailedColorSamples != 0 || numFailedDepthSamples != 0)
  			{
  				m_testCtx.getLog() << TestLog::Image("ErrorMask", "Error mask", errorMask);
  
  				if (numFailedColorSamples != 0)
  					m_testCtx.getLog() << TestLog::Message << "FAIL: Found " << numFailedColorSamples << " invalid color samples!" << TestLog::EndMessage;
  
  				if (numFailedDepthSamples != 0)
  					m_testCtx.getLog() << TestLog::Message << "FAIL: Found " << numFailedDepthSamples << " invalid depth samples!" << TestLog::EndMessage;
  
  				if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
  					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid samples found");
  			}
  
  			m_testCtx.getLog() << TestLog::Message << (numCoveredSamples-numFailedColorSamples) << " / " << numCoveredSamples << " color samples passed" << TestLog::EndMessage;
  			m_testCtx.getLog() << TestLog::Message << (numCoveredSamples-numFailedDepthSamples) << " / " << numCoveredSamples << " depth samples passed" << TestLog::EndMessage;
  		}
  	}
  
  	vector<SubCase>					m_cases;
  	vector<SubCase>::const_iterator	m_caseIter;
  };
  
  class CommonFrameworkTests : public tcu::TestCaseGroup
  {
  public:
  	CommonFrameworkTests (tcu::TestContext& testCtx)
  		: tcu::TestCaseGroup(testCtx, "common", "Tests for the common utility framework")
  	{
  	}
  
  	void init (void)
  	{
  		addChild(new SelfCheckCase(m_testCtx, "float_format","tcu::FloatFormat_selfTest()",
  								   tcu::FloatFormat_selfTest));
  		addChild(new SelfCheckCase(m_testCtx, "either","tcu::Either_selfTest()",
  								   tcu::Either_selfTest));
  	}
  };
  
  class ReferenceRendererTests : public tcu::TestCaseGroup
  {
  public:
  	ReferenceRendererTests (tcu::TestContext& testCtx)
  		: tcu::TestCaseGroup(testCtx, "reference_renderer", "Reference renderer tests")
  	{
  	}
  
  	void init (void)
  	{
  		addChild(new ConstantInterpolationTest(m_testCtx));
  	}
  };
  
  } // anonymous
  
  FrameworkTests::FrameworkTests (tcu::TestContext& testCtx)
  	: tcu::TestCaseGroup(testCtx, "framework", "Miscellaneous framework tests")
  {
  }
  
  FrameworkTests::~FrameworkTests (void)
  {
  }
  
  void FrameworkTests::init (void)
  {
  	addChild(new CommonFrameworkTests	(m_testCtx));
  	addChild(new CaseListParserTests	(m_testCtx));
  	addChild(new ReferenceRendererTests	(m_testCtx));
  	addChild(createTextureFormatTests	(m_testCtx));
  	addChild(createAstcTests			(m_testCtx));
  	addChild(createVulkanTests			(m_testCtx));
  }
  
  } // dit
  