1#include "factory.h"
2#include "integer.h"
3#include "filters.h"
4#include "hex.h"
5#include "randpool.h"
6#include "files.h"
7#include "trunhash.h"
8#include "queue.h"
9#include "validate.h"
10#include <iostream>
11#include <memory>
12
13USING_NAMESPACE(CryptoPP)
14USING_NAMESPACE(std)
15
16typedef std::map<std::string, std::string> TestData;
17
18class TestFailure : public Exception
19{
20public:
21	TestFailure() : Exception(OTHER_ERROR, "Validation test failed") {}
22};
23
24static const TestData *s_currentTestData = NULL;
25
26static void OutputTestData(const TestData &v)
27{
28	for (TestData::const_iterator i = v.begin(); i != v.end(); ++i)
29	{
30		cerr << i->first << ": " << i->second << endl;
31	}
32}
33
34static void SignalTestFailure()
35{
36	OutputTestData(*s_currentTestData);
37	throw TestFailure();
38}
39
40static void SignalTestError()
41{
42	OutputTestData(*s_currentTestData);
43	throw Exception(Exception::OTHER_ERROR, "Unexpected error during validation test");
44}
45
46bool DataExists(const TestData &data, const char *name)
47{
48	TestData::const_iterator i = data.find(name);
49	return (i != data.end());
50}
51
52const std::string & GetRequiredDatum(const TestData &data, const char *name)
53{
54	TestData::const_iterator i = data.find(name);
55	if (i == data.end())
56		SignalTestError();
57	return i->second;
58}
59
60void PutDecodedDatumInto(const TestData &data, const char *name, BufferedTransformation &target)
61{
62	std::string s1 = GetRequiredDatum(data, name), s2;
63
64	while (!s1.empty())
65	{
66		while (s1[0] == ' ')
67			s1 = s1.substr(1);
68
69		int repeat = 1;
70		if (s1[0] == 'r')
71		{
72			repeat = atoi(s1.c_str()+1);
73			s1 = s1.substr(s1.find(' ')+1);
74		}
75
76		s2 = ""; // MSVC 6 doesn't have clear();
77
78		if (s1[0] == '\"')
79		{
80			s2 = s1.substr(1, s1.find('\"', 1)-1);
81			s1 = s1.substr(s2.length() + 2);
82		}
83		else if (s1.substr(0, 2) == "0x")
84		{
85			StringSource(s1.substr(2, s1.find(' ')), true, new HexDecoder(new StringSink(s2)));
86			s1 = s1.substr(STDMIN(s1.find(' '), s1.length()));
87		}
88		else
89		{
90			StringSource(s1.substr(0, s1.find(' ')), true, new HexDecoder(new StringSink(s2)));
91			s1 = s1.substr(STDMIN(s1.find(' '), s1.length()));
92		}
93
94		ByteQueue q;
95		while (repeat--)
96		{
97			q.Put((const byte *)s2.data(), s2.size());
98			if (q.MaxRetrievable() > 4*1024 || repeat == 0)
99				q.TransferTo(target);
100		}
101	}
102}
103
104std::string GetDecodedDatum(const TestData &data, const char *name)
105{
106	std::string s;
107	PutDecodedDatumInto(data, name, StringSink(s).Ref());
108	return s;
109}
110
111std::string GetOptionalDecodedDatum(const TestData &data, const char *name)
112{
113	std::string s;
114	if (DataExists(data, name))
115		PutDecodedDatumInto(data, name, StringSink(s).Ref());
116	return s;
117}
118
119class TestDataNameValuePairs : public NameValuePairs
120{
121public:
122	TestDataNameValuePairs(const TestData &data) : m_data(data) {}
123
124	virtual bool GetVoidValue(const char *name, const std::type_info &valueType, void *pValue) const
125	{
126		TestData::const_iterator i = m_data.find(name);
127		if (i == m_data.end())
128		{
129			if (std::string(name) == Name::DigestSize() && valueType == typeid(int))
130			{
131				i = m_data.find("MAC");
132				if (i == m_data.end())
133					i = m_data.find("Digest");
134				if (i == m_data.end())
135					return false;
136
137				m_temp.resize(0);
138				PutDecodedDatumInto(m_data, i->first.c_str(), StringSink(m_temp).Ref());
139				*reinterpret_cast<int *>(pValue) = (int)m_temp.size();
140				return true;
141			}
142			else
143				return false;
144		}
145
146		const std::string &value = i->second;
147
148		if (valueType == typeid(int))
149			*reinterpret_cast<int *>(pValue) = atoi(value.c_str());
150		else if (valueType == typeid(Integer))
151			*reinterpret_cast<Integer *>(pValue) = Integer((std::string(value) + "h").c_str());
152		else if (valueType == typeid(ConstByteArrayParameter))
153		{
154			m_temp.resize(0);
155			PutDecodedDatumInto(m_data, name, StringSink(m_temp).Ref());
156			reinterpret_cast<ConstByteArrayParameter *>(pValue)->Assign((const byte *)m_temp.data(), m_temp.size(), false);
157		}
158		else
159			throw ValueTypeMismatch(name, typeid(std::string), valueType);
160
161		return true;
162	}
163
164private:
165	const TestData &m_data;
166	mutable std::string m_temp;
167};
168
169void TestKeyPairValidAndConsistent(CryptoMaterial &pub, const CryptoMaterial &priv)
170{
171	if (!pub.Validate(GlobalRNG(), 3))
172		SignalTestFailure();
173	if (!priv.Validate(GlobalRNG(), 3))
174		SignalTestFailure();
175
176/*	EqualityComparisonFilter comparison;
177	pub.Save(ChannelSwitch(comparison, "0"));
178	pub.AssignFrom(priv);
179	pub.Save(ChannelSwitch(comparison, "1"));
180	comparison.ChannelMessageSeriesEnd("0");
181	comparison.ChannelMessageSeriesEnd("1");
182*/
183}
184
185void TestSignatureScheme(TestData &v)
186{
187	std::string name = GetRequiredDatum(v, "Name");
188	std::string test = GetRequiredDatum(v, "Test");
189
190	std::auto_ptr<PK_Signer> signer(ObjectFactoryRegistry<PK_Signer>::Registry().CreateObject(name.c_str()));
191	std::auto_ptr<PK_Verifier> verifier(ObjectFactoryRegistry<PK_Verifier>::Registry().CreateObject(name.c_str()));
192
193	TestDataNameValuePairs pairs(v);
194	std::string keyFormat = GetRequiredDatum(v, "KeyFormat");
195
196	if (keyFormat == "DER")
197		verifier->AccessMaterial().Load(StringStore(GetDecodedDatum(v, "PublicKey")).Ref());
198	else if (keyFormat == "Component")
199		verifier->AccessMaterial().AssignFrom(pairs);
200
201	if (test == "Verify" || test == "NotVerify")
202	{
203		VerifierFilter verifierFilter(*verifier, NULL, VerifierFilter::SIGNATURE_AT_BEGIN);
204		PutDecodedDatumInto(v, "Signature", verifierFilter);
205		PutDecodedDatumInto(v, "Message", verifierFilter);
206		verifierFilter.MessageEnd();
207		if (verifierFilter.GetLastResult() == (test == "NotVerify"))
208			SignalTestFailure();
209	}
210	else if (test == "PublicKeyValid")
211	{
212		if (!verifier->GetMaterial().Validate(GlobalRNG(), 3))
213			SignalTestFailure();
214	}
215	else
216		goto privateKeyTests;
217
218	return;
219
220privateKeyTests:
221	if (keyFormat == "DER")
222		signer->AccessMaterial().Load(StringStore(GetDecodedDatum(v, "PrivateKey")).Ref());
223	else if (keyFormat == "Component")
224		signer->AccessMaterial().AssignFrom(pairs);
225
226	if (test == "KeyPairValidAndConsistent")
227	{
228		TestKeyPairValidAndConsistent(verifier->AccessMaterial(), signer->GetMaterial());
229	}
230	else if (test == "Sign")
231	{
232		SignerFilter f(GlobalRNG(), *signer, new HexEncoder(new FileSink(cout)));
233		StringSource ss(GetDecodedDatum(v, "Message"), true, new Redirector(f));
234		SignalTestFailure();
235	}
236	else if (test == "DeterministicSign")
237	{
238		SignalTestError();
239		assert(false);	// TODO: implement
240	}
241	else if (test == "RandomSign")
242	{
243		SignalTestError();
244		assert(false);	// TODO: implement
245	}
246	else if (test == "GenerateKey")
247	{
248		SignalTestError();
249		assert(false);
250	}
251	else
252	{
253		SignalTestError();
254		assert(false);
255	}
256}
257
258void TestAsymmetricCipher(TestData &v)
259{
260	std::string name = GetRequiredDatum(v, "Name");
261	std::string test = GetRequiredDatum(v, "Test");
262
263	std::auto_ptr<PK_Encryptor> encryptor(ObjectFactoryRegistry<PK_Encryptor>::Registry().CreateObject(name.c_str()));
264	std::auto_ptr<PK_Decryptor> decryptor(ObjectFactoryRegistry<PK_Decryptor>::Registry().CreateObject(name.c_str()));
265
266	std::string keyFormat = GetRequiredDatum(v, "KeyFormat");
267
268	if (keyFormat == "DER")
269	{
270		decryptor->AccessMaterial().Load(StringStore(GetDecodedDatum(v, "PrivateKey")).Ref());
271		encryptor->AccessMaterial().Load(StringStore(GetDecodedDatum(v, "PublicKey")).Ref());
272	}
273	else if (keyFormat == "Component")
274	{
275		TestDataNameValuePairs pairs(v);
276		decryptor->AccessMaterial().AssignFrom(pairs);
277		encryptor->AccessMaterial().AssignFrom(pairs);
278	}
279
280	if (test == "DecryptMatch")
281	{
282		std::string decrypted, expected = GetDecodedDatum(v, "Plaintext");
283		StringSource ss(GetDecodedDatum(v, "Ciphertext"), true, new PK_DecryptorFilter(GlobalRNG(), *decryptor, new StringSink(decrypted)));
284		if (decrypted != expected)
285			SignalTestFailure();
286	}
287	else if (test == "KeyPairValidAndConsistent")
288	{
289		TestKeyPairValidAndConsistent(encryptor->AccessMaterial(), decryptor->GetMaterial());
290	}
291	else
292	{
293		SignalTestError();
294		assert(false);
295	}
296}
297
298void TestSymmetricCipher(TestData &v, const NameValuePairs &overrideParameters)
299{
300	std::string name = GetRequiredDatum(v, "Name");
301	std::string test = GetRequiredDatum(v, "Test");
302
303	std::string key = GetDecodedDatum(v, "Key");
304	std::string plaintext = GetDecodedDatum(v, "Plaintext");
305
306	TestDataNameValuePairs testDataPairs(v);
307	CombinedNameValuePairs pairs(overrideParameters, testDataPairs);
308
309	if (test == "Encrypt" || test == "EncryptXorDigest" || test == "Resync")
310	{
311		static member_ptr<SymmetricCipher> encryptor, decryptor;
312		static std::string lastName;
313
314		if (name != lastName)
315		{
316			encryptor.reset(ObjectFactoryRegistry<SymmetricCipher, ENCRYPTION>::Registry().CreateObject(name.c_str()));
317			decryptor.reset(ObjectFactoryRegistry<SymmetricCipher, DECRYPTION>::Registry().CreateObject(name.c_str()));
318			lastName = name;
319		}
320
321		ConstByteArrayParameter iv;
322		if (pairs.GetValue(Name::IV(), iv) && iv.size() != encryptor->IVSize())
323			SignalTestFailure();
324
325		if (test == "Resync")
326		{
327			encryptor->Resynchronize(iv.begin(), (int)iv.size());
328			decryptor->Resynchronize(iv.begin(), (int)iv.size());
329		}
330		else
331		{
332			encryptor->SetKey((const byte *)key.data(), key.size(), pairs);
333			decryptor->SetKey((const byte *)key.data(), key.size(), pairs);
334		}
335
336		int seek = pairs.GetIntValueWithDefault("Seek", 0);
337		if (seek)
338		{
339			encryptor->Seek(seek);
340			decryptor->Seek(seek);
341		}
342		std::string encrypted, xorDigest, ciphertext, ciphertextXorDigest;
343		StringSource ss(plaintext, false, new StreamTransformationFilter(*encryptor, new StringSink(encrypted), StreamTransformationFilter::NO_PADDING));
344		ss.Pump(plaintext.size()/2 + 1);
345		ss.PumpAll();
346		/*{
347			std::string z;
348			encryptor->Seek(seek);
349			StringSource ss(plaintext, false, new StreamTransformationFilter(*encryptor, new StringSink(z), StreamTransformationFilter::NO_PADDING));
350			while (ss.Pump(64)) {}
351			ss.PumpAll();
352			for (int i=0; i<z.length(); i++)
353				assert(encrypted[i] == z[i]);
354		}*/
355		if (test != "EncryptXorDigest")
356			ciphertext = GetDecodedDatum(v, "Ciphertext");
357		else
358		{
359			ciphertextXorDigest = GetDecodedDatum(v, "CiphertextXorDigest");
360			xorDigest.append(encrypted, 0, 64);
361			for (size_t i=64; i<encrypted.size(); i++)
362				xorDigest[i%64] ^= encrypted[i];
363		}
364		if (test != "EncryptXorDigest" ? encrypted != ciphertext : xorDigest != ciphertextXorDigest)
365		{
366			std::cout << "incorrectly encrypted: ";
367			StringSource xx(encrypted, false, new HexEncoder(new FileSink(std::cout)));
368			xx.Pump(256); xx.Flush(false);
369			std::cout << "\n";
370			SignalTestFailure();
371		}
372		std::string decrypted;
373		StringSource dd(encrypted, false, new StreamTransformationFilter(*decryptor, new StringSink(decrypted), StreamTransformationFilter::NO_PADDING));
374		dd.Pump(plaintext.size()/2 + 1);
375		dd.PumpAll();
376		if (decrypted != plaintext)
377		{
378			std::cout << "incorrectly decrypted: ";
379			StringSource xx(decrypted, false, new HexEncoder(new FileSink(std::cout)));
380			xx.Pump(256); xx.Flush(false);
381			std::cout << "\n";
382			SignalTestFailure();
383		}
384	}
385	else
386	{
387		std::cout << "unexpected test name\n";
388		SignalTestError();
389	}
390}
391
392void TestAuthenticatedSymmetricCipher(TestData &v, const NameValuePairs &overrideParameters)
393{
394	std::string type = GetRequiredDatum(v, "AlgorithmType");
395	std::string name = GetRequiredDatum(v, "Name");
396	std::string test = GetRequiredDatum(v, "Test");
397	std::string key = GetDecodedDatum(v, "Key");
398
399	std::string plaintext = GetOptionalDecodedDatum(v, "Plaintext");
400	std::string ciphertext = GetOptionalDecodedDatum(v, "Ciphertext");
401	std::string header = GetOptionalDecodedDatum(v, "Header");
402	std::string footer = GetOptionalDecodedDatum(v, "Footer");
403	std::string mac = GetOptionalDecodedDatum(v, "MAC");
404
405	TestDataNameValuePairs testDataPairs(v);
406	CombinedNameValuePairs pairs(overrideParameters, testDataPairs);
407
408	if (test == "Encrypt" || test == "EncryptXorDigest" || test == "NotVerify")
409	{
410		member_ptr<AuthenticatedSymmetricCipher> asc1, asc2;
411		asc1.reset(ObjectFactoryRegistry<AuthenticatedSymmetricCipher, ENCRYPTION>::Registry().CreateObject(name.c_str()));
412		asc2.reset(ObjectFactoryRegistry<AuthenticatedSymmetricCipher, DECRYPTION>::Registry().CreateObject(name.c_str()));
413		asc1->SetKey((const byte *)key.data(), key.size(), pairs);
414		asc2->SetKey((const byte *)key.data(), key.size(), pairs);
415
416		std::string encrypted, decrypted;
417		AuthenticatedEncryptionFilter ef(*asc1, new StringSink(encrypted));
418		bool macAtBegin = !mac.empty() && !GlobalRNG().GenerateBit();	// test both ways randomly
419		AuthenticatedDecryptionFilter df(*asc2, new StringSink(decrypted), macAtBegin ? AuthenticatedDecryptionFilter::MAC_AT_BEGIN : 0);
420
421		if (asc1->NeedsPrespecifiedDataLengths())
422		{
423			asc1->SpecifyDataLengths(header.size(), plaintext.size(), footer.size());
424			asc2->SpecifyDataLengths(header.size(), plaintext.size(), footer.size());
425		}
426
427		StringStore sh(header), sp(plaintext), sc(ciphertext), sf(footer), sm(mac);
428
429		if (macAtBegin)
430			sm.TransferTo(df);
431		sh.CopyTo(df, LWORD_MAX, AAD_CHANNEL);
432		sc.TransferTo(df);
433		sf.CopyTo(df, LWORD_MAX, AAD_CHANNEL);
434		if (!macAtBegin)
435			sm.TransferTo(df);
436		df.MessageEnd();
437
438		sh.TransferTo(ef, sh.MaxRetrievable()/2+1, AAD_CHANNEL);
439		sh.TransferTo(ef, LWORD_MAX, AAD_CHANNEL);
440		sp.TransferTo(ef, sp.MaxRetrievable()/2+1);
441		sp.TransferTo(ef);
442		sf.TransferTo(ef, sf.MaxRetrievable()/2+1, AAD_CHANNEL);
443		sf.TransferTo(ef, LWORD_MAX, AAD_CHANNEL);
444		ef.MessageEnd();
445
446		if (test == "Encrypt" && encrypted != ciphertext+mac)
447		{
448			std::cout << "incorrectly encrypted: ";
449			StringSource xx(encrypted, false, new HexEncoder(new FileSink(std::cout)));
450			xx.Pump(256); xx.Flush(false);
451			std::cout << "\n";
452			SignalTestFailure();
453		}
454		if (test == "Encrypt" && decrypted != plaintext)
455		{
456			std::cout << "incorrectly decrypted: ";
457			StringSource xx(decrypted, false, new HexEncoder(new FileSink(std::cout)));
458			xx.Pump(256); xx.Flush(false);
459			std::cout << "\n";
460			SignalTestFailure();
461		}
462
463		if (ciphertext.size()+mac.size()-plaintext.size() != asc1->DigestSize())
464		{
465			std::cout << "bad MAC size\n";
466			SignalTestFailure();
467		}
468		if (df.GetLastResult() != (test == "Encrypt"))
469		{
470			std::cout << "MAC incorrectly verified\n";
471			SignalTestFailure();
472		}
473	}
474	else
475	{
476		std::cout << "unexpected test name\n";
477		SignalTestError();
478	}
479}
480
481void TestDigestOrMAC(TestData &v, bool testDigest)
482{
483	std::string name = GetRequiredDatum(v, "Name");
484	std::string test = GetRequiredDatum(v, "Test");
485	const char *digestName = testDigest ? "Digest" : "MAC";
486
487	member_ptr<MessageAuthenticationCode> mac;
488	member_ptr<HashTransformation> hash;
489	HashTransformation *pHash = NULL;
490
491	TestDataNameValuePairs pairs(v);
492
493	if (testDigest)
494	{
495		hash.reset(ObjectFactoryRegistry<HashTransformation>::Registry().CreateObject(name.c_str()));
496		pHash = hash.get();
497	}
498	else
499	{
500		mac.reset(ObjectFactoryRegistry<MessageAuthenticationCode>::Registry().CreateObject(name.c_str()));
501		pHash = mac.get();
502		std::string key = GetDecodedDatum(v, "Key");
503		mac->SetKey((const byte *)key.c_str(), key.size(), pairs);
504	}
505
506	if (test == "Verify" || test == "VerifyTruncated" || test == "NotVerify")
507	{
508		int digestSize = -1;
509		if (test == "VerifyTruncated")
510			pairs.GetIntValue(Name::DigestSize(), digestSize);
511		HashVerificationFilter verifierFilter(*pHash, NULL, HashVerificationFilter::HASH_AT_BEGIN, digestSize);
512		PutDecodedDatumInto(v, digestName, verifierFilter);
513		PutDecodedDatumInto(v, "Message", verifierFilter);
514		verifierFilter.MessageEnd();
515		if (verifierFilter.GetLastResult() == (test == "NotVerify"))
516			SignalTestFailure();
517	}
518	else
519	{
520		SignalTestError();
521		assert(false);
522	}
523}
524
525bool GetField(std::istream &is, std::string &name, std::string &value)
526{
527	name.resize(0);		// GCC workaround: 2.95.3 doesn't have clear()
528	is >> name;
529	if (name.empty())
530		return false;
531
532	if (name[name.size()-1] != ':')
533	{
534		char c;
535		is >> skipws >> c;
536		if (c != ':')
537			SignalTestError();
538	}
539	else
540		name.erase(name.size()-1);
541
542	while (is.peek() == ' ')
543		is.ignore(1);
544
545	// VC60 workaround: getline bug
546	char buffer[128];
547	value.resize(0);	// GCC workaround: 2.95.3 doesn't have clear()
548	bool continueLine;
549
550	do
551	{
552		do
553		{
554			is.get(buffer, sizeof(buffer));
555			value += buffer;
556		}
557		while (buffer[0] != 0);
558		is.clear();
559		is.ignore();
560
561		if (!value.empty() && value[value.size()-1] == '\r')
562			value.resize(value.size()-1);
563
564		if (!value.empty() && value[value.size()-1] == '\\')
565		{
566			value.resize(value.size()-1);
567			continueLine = true;
568		}
569		else
570			continueLine = false;
571
572		std::string::size_type i = value.find('#');
573		if (i != std::string::npos)
574			value.erase(i);
575	}
576	while (continueLine);
577
578	return true;
579}
580
581void OutputPair(const NameValuePairs &v, const char *name)
582{
583	Integer x;
584	bool b = v.GetValue(name, x);
585	assert(b);
586	cout << name << ": \\\n    ";
587	x.Encode(HexEncoder(new FileSink(cout), false, 64, "\\\n    ").Ref(), x.MinEncodedSize());
588	cout << endl;
589}
590
591void OutputNameValuePairs(const NameValuePairs &v)
592{
593	std::string names = v.GetValueNames();
594	string::size_type i = 0;
595	while (i < names.size())
596	{
597		string::size_type j = names.find_first_of (';', i);
598
599		if (j == string::npos)
600			return;
601		else
602		{
603			std::string name = names.substr(i, j-i);
604			if (name.find(':') == string::npos)
605				OutputPair(v, name.c_str());
606		}
607
608		i = j + 1;
609	}
610}
611
612void TestDataFile(const std::string &filename, const NameValuePairs &overrideParameters, unsigned int &totalTests, unsigned int &failedTests)
613{
614	std::ifstream file(filename.c_str());
615	if (!file.good())
616		throw Exception(Exception::OTHER_ERROR, "Can not open file " + filename + " for reading");
617	TestData v;
618	s_currentTestData = &v;
619	std::string name, value, lastAlgName;
620
621	while (file)
622	{
623		while (file.peek() == '#')
624			file.ignore(INT_MAX, '\n');
625
626		if (file.peek() == '\n' || file.peek() == '\r')
627			v.clear();
628
629		if (!GetField(file, name, value))
630			break;
631		v[name] = value;
632
633		if (name == "Test")
634		{
635			bool failed = true;
636			std::string algType = GetRequiredDatum(v, "AlgorithmType");
637
638			if (lastAlgName != GetRequiredDatum(v, "Name"))
639			{
640				lastAlgName = GetRequiredDatum(v, "Name");
641				cout << "\nTesting " << algType.c_str() << " algorithm " << lastAlgName.c_str() << ".\n";
642			}
643
644			try
645			{
646				if (algType == "Signature")
647					TestSignatureScheme(v);
648				else if (algType == "SymmetricCipher")
649					TestSymmetricCipher(v, overrideParameters);
650				else if (algType == "AuthenticatedSymmetricCipher")
651					TestAuthenticatedSymmetricCipher(v, overrideParameters);
652				else if (algType == "AsymmetricCipher")
653					TestAsymmetricCipher(v);
654				else if (algType == "MessageDigest")
655					TestDigestOrMAC(v, true);
656				else if (algType == "MAC")
657					TestDigestOrMAC(v, false);
658				else if (algType == "FileList")
659					TestDataFile(GetRequiredDatum(v, "Test"), g_nullNameValuePairs, totalTests, failedTests);
660				else
661					SignalTestError();
662				failed = false;
663			}
664			catch (TestFailure &)
665			{
666				cout << "\nTest failed.\n";
667			}
668			catch (CryptoPP::Exception &e)
669			{
670				cout << "\nCryptoPP::Exception caught: " << e.what() << endl;
671			}
672			catch (std::exception &e)
673			{
674				cout << "\nstd::exception caught: " << e.what() << endl;
675			}
676
677			if (failed)
678			{
679				cout << "Skipping to next test.\n";
680				failedTests++;
681			}
682			else
683				cout << "." << flush;
684
685			totalTests++;
686		}
687	}
688}
689
690bool RunTestDataFile(const char *filename, const NameValuePairs &overrideParameters)
691{
692	unsigned int totalTests = 0, failedTests = 0;
693	TestDataFile(filename, overrideParameters, totalTests, failedTests);
694	cout << "\nTests complete. Total tests = " << totalTests << ". Failed tests = " << failedTests << ".\n";
695	if (failedTests != 0)
696		cout << "SOME TESTS FAILED!\n";
697	return failedTests == 0;
698}
699