PageRenderTime 86ms CodeModel.GetById 15ms app.highlight 64ms RepoModel.GetById 2ms app.codeStats 0ms

/indra/llcommon/tests/llerror_test.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 762 lines | 576 code | 114 blank | 72 comment | 3 complexity | 3927d0e4bc6a41f3336f016e5702b3d5 MD5 | raw file
  1/** 
  2 * @file llerror_test.cpp
  3 * @date   December 2006
  4 * @brief error unit tests
  5 *
  6 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
  7 * Second Life Viewer Source Code
  8 * Copyright (C) 2010, Linden Research, Inc.
  9 * 
 10 * This library is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU Lesser General Public
 12 * License as published by the Free Software Foundation;
 13 * version 2.1 of the License only.
 14 * 
 15 * This library is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 18 * Lesser General Public License for more details.
 19 * 
 20 * You should have received a copy of the GNU Lesser General Public
 21 * License along with this library; if not, write to the Free Software
 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 23 * 
 24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 25 * $/LicenseInfo$
 26 */
 27
 28#include <vector>
 29
 30#include "linden_common.h"
 31
 32#include "../llerror.h"
 33
 34#include "../llerrorcontrol.h"
 35#include "../llsd.h"
 36
 37#include "../test/lltut.h"
 38
 39namespace
 40{
 41	void test_that_error_h_includes_enough_things_to_compile_a_message()
 42	{
 43		llinfos << "!" << llendl;
 44	}
 45}
 46
 47namespace
 48{
 49	static bool fatalWasCalled;
 50	void fatalCall(const std::string&) { fatalWasCalled = true; }
 51}
 52	
 53namespace tut
 54{
 55	class TestRecorder : public LLError::Recorder
 56	{
 57	public:
 58		TestRecorder() : mWantsTime(false) { }
 59		~TestRecorder() { LLError::removeRecorder(this); }
 60		
 61		void recordMessage(LLError::ELevel level,
 62						   const std::string& message)
 63		{
 64			mMessages.push_back(message);
 65		}
 66		
 67		int countMessages()			{ return (int) mMessages.size(); }
 68		void clearMessages()		{ mMessages.clear(); }
 69		
 70		void setWantsTime(bool t)	{ mWantsTime = t; }
 71		bool wantsTime()			{ return mWantsTime; }
 72		
 73		std::string message(int n)
 74		{
 75			std::ostringstream test_name;
 76			test_name << "testing message " << n << ", not enough messages";
 77			
 78			tut::ensure(test_name.str(), n < countMessages());
 79			return mMessages[n];
 80		}
 81		
 82	private:
 83		typedef std::vector<std::string> MessageVector;
 84		MessageVector mMessages;
 85		
 86		bool mWantsTime;
 87	};
 88
 89	struct ErrorTestData
 90	{
 91		TestRecorder mRecorder;
 92		LLError::Settings* mPriorErrorSettings;
 93		
 94		ErrorTestData()
 95		{
 96			fatalWasCalled = false;
 97			
 98			mPriorErrorSettings = LLError::saveAndResetSettings();
 99			LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
100			LLError::setFatalFunction(fatalCall);
101			LLError::addRecorder(&mRecorder);
102		}
103		
104		~ErrorTestData()
105		{
106			LLError::removeRecorder(&mRecorder);
107			LLError::restoreSettings(mPriorErrorSettings);
108		}
109		
110		void ensure_message_count(int expectedCount)
111		{
112			ensure_equals("message count", mRecorder.countMessages(), expectedCount);
113		}
114
115		void ensure_message_contains(int n, const std::string& expectedText)
116		{
117			std::ostringstream test_name;
118			test_name << "testing message " << n;
119
120			ensure_contains(test_name.str(), mRecorder.message(n), expectedText);
121		}
122
123		void ensure_message_does_not_contain(int n, const std::string& expectedText)
124		{
125			std::ostringstream test_name;
126			test_name << "testing message " << n;
127
128			ensure_does_not_contain(test_name.str(), mRecorder.message(n), expectedText);
129		}
130	};
131	
132	typedef test_group<ErrorTestData>	ErrorTestGroup;
133	typedef ErrorTestGroup::object		ErrorTestObject;
134	
135	ErrorTestGroup errorTestGroup("error");
136	
137	template<> template<>
138	void ErrorTestObject::test<1>()
139		// basic test of output
140	{
141		llinfos << "test" << llendl;
142		llinfos << "bob" << llendl;
143		
144		ensure_message_contains(0, "test");
145		ensure_message_contains(1, "bob");
146	}
147}
148
149namespace
150{
151	void writeSome()
152	{
153		lldebugs << "one" << llendl;
154		llinfos << "two" << llendl;
155		llwarns << "three" << llendl;
156		llerrs << "four" << llendl;
157			// fatal messages write out and addtional "error" message
158	}
159};
160
161namespace tut
162{	
163	template<> template<>
164	void ErrorTestObject::test<2>()
165		// messages are filtered based on default level
166	{
167		LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
168		writeSome();
169		ensure_message_contains(0, "one");
170		ensure_message_contains(1, "two");
171		ensure_message_contains(2, "three");
172		ensure_message_contains(3, "error");
173		ensure_message_contains(4, "four");
174		ensure_message_count(5);
175	
176		LLError::setDefaultLevel(LLError::LEVEL_INFO);
177		writeSome();
178		ensure_message_contains(5, "two");
179		ensure_message_contains(6, "three");
180		ensure_message_contains(7, "error");
181		ensure_message_contains(8, "four");
182		ensure_message_count(9);
183		
184		LLError::setDefaultLevel(LLError::LEVEL_WARN);
185		writeSome();
186		ensure_message_contains(9, "three");
187		ensure_message_contains(10, "error");
188		ensure_message_contains(11, "four");
189		ensure_message_count(12);
190		
191		LLError::setDefaultLevel(LLError::LEVEL_ERROR);
192		writeSome();
193		ensure_message_contains(12, "error");
194		ensure_message_contains(13, "four");
195		ensure_message_count(14);
196		
197		LLError::setDefaultLevel(LLError::LEVEL_NONE);
198		writeSome();
199		ensure_message_count(14);
200	}
201
202	template<> template<>
203	void ErrorTestObject::test<3>()
204		// error type string in output
205	{
206		writeSome();
207		ensure_message_contains(0, "DEBUG: ");
208		ensure_message_contains(1, "INFO: ");
209		ensure_message_contains(2, "WARNING: ");
210		ensure_message_does_not_contain(3, "ERROR");
211		ensure_message_contains(4, "ERROR: ");
212		ensure_message_count(5);
213	}
214
215	template<> template<>
216	void ErrorTestObject::test<4>()
217		// file abbreviation
218	{
219		std::string thisFile = __FILE__;
220		std::string abbreviateFile = LLError::abbreviateFile(thisFile);
221		
222		ensure_ends_with("file name abbreviation",
223			abbreviateFile,
224			"llcommon/tests/llerror_test.cpp"
225			);
226		ensure_does_not_contain("file name abbreviation",
227			abbreviateFile, "indra");
228			
229		std::string someFile =
230#if LL_WINDOWS
231			"C:/amy/bob/cam.cpp"
232#else
233			"/amy/bob/cam.cpp"
234#endif
235			;
236		std::string someAbbreviation = LLError::abbreviateFile(someFile);
237		
238		ensure_equals("non-indra file abbreviation",
239			someAbbreviation, someFile);
240	}
241}
242	
243namespace
244{
245	std::string locationString(int line)
246	{
247		std::ostringstream location;
248		location << LLError::abbreviateFile(__FILE__)
249				 << "(" << line << ") : ";
250				 
251		return location.str();
252	}
253	
254	std::string writeReturningLocation()
255	{
256		llinfos << "apple" << llendl;	int this_line = __LINE__;
257		return locationString(this_line);
258	}
259	
260	std::string writeReturningLocationAndFunction()
261	{
262		llinfos << "apple" << llendl;	int this_line = __LINE__;
263		return locationString(this_line) + __FUNCTION__;
264	}
265	
266	std::string errorReturningLocation()
267	{
268		llerrs << "die" << llendl;	int this_line = __LINE__;
269		return locationString(this_line);
270	}
271}
272
273namespace tut
274{	
275	template<> template<>
276	void ErrorTestObject::test<5>()
277		// file and line information in log messages
278	{
279		std::string location = writeReturningLocation();
280			// expecting default to not print location information
281		
282		LLError::setPrintLocation(true);
283		writeReturningLocation();
284		
285		LLError::setPrintLocation(false);
286		writeReturningLocation();
287		
288		ensure_message_does_not_contain(0, location);
289		ensure_message_contains(1, location);
290		ensure_message_does_not_contain(2, location);
291	}
292}
293
294/* The following helper functions and class members all log a simple message
295	from some particular function scope.  Each function takes a bool argument
296	that indicates if it should log its own name or not (in the manner that
297	existing log messages often do.)  The functions all return their C++
298	name so that test can be substantial mechanized.
299 */
300 
301std::string logFromGlobal(bool id)
302{
303	llinfos << (id ? "logFromGlobal: " : "") << "hi" << llendl;
304	return "logFromGlobal";
305}
306
307static std::string logFromStatic(bool id)
308{
309	llinfos << (id ? "logFromStatic: " : "") << "hi" << llendl;
310	return "logFromStatic";
311}
312
313namespace
314{
315	std::string logFromAnon(bool id)
316	{
317		llinfos << (id ? "logFromAnon: " : "") << "hi" << llendl;
318		return "logFromAnon";
319	}
320}
321
322namespace Foo {
323	std::string logFromNamespace(bool id)
324	{
325		llinfos << (id ? "Foo::logFromNamespace: " : "") << "hi" << llendl;
326		//return "Foo::logFromNamespace";
327			// there is no standard way to get the namespace name, hence
328			// we won't be testing for it
329		return "logFromNamespace";
330	}
331}
332
333namespace
334{
335	class ClassWithNoLogType {
336	public:
337		std::string logFromMember(bool id)
338		{
339			llinfos << (id ? "ClassWithNoLogType::logFromMember: " : "") << "hi" << llendl;
340			return "ClassWithNoLogType::logFromMember";
341		}
342		static std::string logFromStatic(bool id)
343		{
344			llinfos << (id ? "ClassWithNoLogType::logFromStatic: " : "") << "hi" << llendl;
345			return "ClassWithNoLogType::logFromStatic";
346		}
347	};
348	
349	class ClassWithLogType {
350		LOG_CLASS(ClassWithLogType);
351	public:
352		std::string logFromMember(bool id)
353		{
354			llinfos << (id ? "ClassWithLogType::logFromMember: " : "") << "hi" << llendl;
355			return "ClassWithLogType::logFromMember";
356		}
357		static std::string logFromStatic(bool id)
358		{
359			llinfos << (id ? "ClassWithLogType::logFromStatic: " : "") << "hi" << llendl;
360			return "ClassWithLogType::logFromStatic";
361		}
362	};
363	
364	std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); }
365	std::string logFromClassWithNoLogTypeMember(bool id) { ClassWithNoLogType c; return c.logFromMember(id); }
366	std::string logFromClassWithNoLogTypeStatic(bool id) { return ClassWithNoLogType::logFromStatic(id); }
367	std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); }
368	std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); }
369	
370	void ensure_has(const std::string& message,
371		const std::string& actual, const std::string& expected)
372	{
373		std::string::size_type n1 = actual.find(expected);
374		if (n1 == std::string::npos)
375		{
376			std::stringstream ss;
377			ss << message << ": " << "expected to find a copy of " << expected
378				<< " in actual " << actual;
379			throw tut::failure(ss.str().c_str());
380		}
381	}
382	
383	typedef std::string (*LogFromFunction)(bool);
384	void testLogName(tut::TestRecorder& recorder, LogFromFunction f,
385		const std::string& class_name = "")
386	{
387		recorder.clearMessages();
388		std::string name = f(false);
389		f(true);
390		
391		std::string messageWithoutName = recorder.message(0);
392		std::string messageWithName = recorder.message(1);
393		
394		ensure_has(name + " logged without name",
395			messageWithoutName, name);
396		ensure_has(name + " logged with name",
397			messageWithName, name);
398
399		if (!class_name.empty())
400		{
401			ensure_has(name + "logged without name",
402				messageWithoutName, class_name);
403			ensure_has(name + "logged with name",
404				messageWithName, class_name);
405		}
406	}
407}
408
409namespace tut
410{
411	template<> template<>
412		// 	class/function information in output
413	void ErrorTestObject::test<6>()
414	{
415		testLogName(mRecorder, logFromGlobal);
416		testLogName(mRecorder, logFromStatic);
417		testLogName(mRecorder, logFromAnon);
418		testLogName(mRecorder, logFromNamespace);
419		//testLogName(mRecorder, logFromClassWithNoLogTypeMember, "ClassWithNoLogType");
420		//testLogName(mRecorder, logFromClassWithNoLogTypeStatic, "ClassWithNoLogType");
421			// XXX: figure out what the exepcted response is for these
422		testLogName(mRecorder, logFromClassWithLogTypeMember, "ClassWithLogType");
423		testLogName(mRecorder, logFromClassWithLogTypeStatic, "ClassWithLogType");
424	}
425}
426
427namespace
428{
429	std::string innerLogger()
430	{
431		llinfos << "inside" << llendl;
432		return "moo";
433	}
434	
435	std::string outerLogger()
436	{
437		llinfos << "outside(" << innerLogger() << ")" << llendl;
438		return "bar";
439	}
440	
441	void uberLogger()
442	{
443		llinfos << "uber(" << outerLogger() << "," << innerLogger() << ")" << llendl;
444	}
445	
446	class LogWhileLogging
447	{
448	public:
449		void print(std::ostream& out) const
450		{
451			llinfos << "logging" << llendl;
452			out << "baz";
453		}
454	};
455
456	std::ostream& operator<<(std::ostream& out, const LogWhileLogging& l)
457		{ l.print(out); return out; }
458
459	void metaLogger()
460	{
461		LogWhileLogging l;
462		llinfos << "meta(" << l << ")" << llendl;
463	}
464	
465}
466
467namespace tut
468{			
469	template<> template<>
470		// handle nested logging
471	void ErrorTestObject::test<7>()
472	{
473		outerLogger();
474		ensure_message_contains(0, "inside");
475		ensure_message_contains(1, "outside(moo)");
476		ensure_message_count(2);
477		
478		uberLogger();
479		ensure_message_contains(2, "inside");
480		ensure_message_contains(3, "inside");
481		ensure_message_contains(4, "outside(moo)");
482		ensure_message_contains(5, "uber(bar,moo)");
483		ensure_message_count(6);
484		
485		metaLogger();
486		ensure_message_contains(6, "logging");
487		ensure_message_contains(7, "meta(baz)");
488		ensure_message_count(8);
489	}
490	
491	template<> template<>
492		// special handling of llerrs calls
493	void ErrorTestObject::test<8>()
494	{
495		LLError::setPrintLocation(false);
496		std::string location = errorReturningLocation();
497		
498		ensure_message_contains(0, location + "error");
499		ensure_message_contains(1, "die");
500		ensure_message_count(2);
501		
502		ensure("fatal callback called", fatalWasCalled);
503	}
504}
505
506namespace
507{
508	std::string roswell()
509	{
510		return "1947-07-08T03:04:05Z";
511	}
512	
513	void ufoSighting()
514	{
515		llinfos << "ufo" << llendl;
516	}
517}
518
519namespace tut
520{	
521	template<> template<>
522		// time in output (for recorders that need it)
523	void ErrorTestObject::test<9>()
524	{
525		LLError::setTimeFunction(roswell);
526
527		mRecorder.setWantsTime(false);
528		ufoSighting();
529		ensure_message_contains(0, "ufo");
530		ensure_message_does_not_contain(0, roswell());
531		
532		mRecorder.setWantsTime(true);
533		ufoSighting();
534		ensure_message_contains(1, "ufo");
535		ensure_message_contains(1, roswell());
536	}
537	
538	template<> template<>
539		// output order
540	void ErrorTestObject::test<10>()
541	{
542		LLError::setPrintLocation(true);
543		LLError::setTimeFunction(roswell);
544		mRecorder.setWantsTime(true);
545		std::string locationAndFunction = writeReturningLocationAndFunction();
546		
547		ensure_equals("order is time type location function message",
548			mRecorder.message(0),
549			roswell() + " INFO: " + locationAndFunction + ": apple");
550	}
551
552	template<> template<>
553		// multiple recorders
554	void ErrorTestObject::test<11>()
555	{
556		TestRecorder altRecorder;
557		LLError::addRecorder(&altRecorder);
558		
559		llinfos << "boo" << llendl;
560
561		ensure_message_contains(0, "boo");
562		ensure_equals("alt recorder count", altRecorder.countMessages(), 1);
563		ensure_contains("alt recorder message 0", altRecorder.message(0), "boo");
564		
565		LLError::setTimeFunction(roswell);
566
567		TestRecorder anotherRecorder;
568		anotherRecorder.setWantsTime(true);
569		LLError::addRecorder(&anotherRecorder);
570		
571		llinfos << "baz" << llendl;
572
573		std::string when = roswell();
574		
575		ensure_message_does_not_contain(1, when);
576		ensure_equals("alt recorder count", altRecorder.countMessages(), 2);
577		ensure_does_not_contain("alt recorder message 1", altRecorder.message(1), when);
578		ensure_equals("another recorder count", anotherRecorder.countMessages(), 1);
579		ensure_contains("another recorder message 0", anotherRecorder.message(0), when);
580	}
581}
582
583class TestAlpha
584{
585	LOG_CLASS(TestAlpha);
586public:
587	static void doDebug()	{ lldebugs << "add dice" << llendl; }
588	static void doInfo()	{ llinfos  << "any idea" << llendl; }
589	static void doWarn()	{ llwarns  << "aim west" << llendl; }
590	static void doError()	{ llerrs   << "ate eels" << llendl; }
591	static void doAll() { doDebug(); doInfo(); doWarn(); doError(); }
592};
593
594class TestBeta
595{
596	LOG_CLASS(TestBeta);
597public:
598	static void doDebug()	{ lldebugs << "bed down" << llendl; }
599	static void doInfo()	{ llinfos  << "buy iron" << llendl; }
600	static void doWarn()	{ llwarns  << "bad word" << llendl; }
601	static void doError()	{ llerrs   << "big easy" << llendl; }
602	static void doAll() { doDebug(); doInfo(); doWarn(); doError(); }
603};
604
605namespace tut
606{
607	template<> template<>
608		// filtering by class
609	void ErrorTestObject::test<12>()
610	{
611		LLError::setDefaultLevel(LLError::LEVEL_WARN);
612		LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO);
613		
614		TestAlpha::doAll();
615		TestBeta::doAll();
616		
617		ensure_message_contains(0, "aim west");
618		ensure_message_contains(1, "error");
619		ensure_message_contains(2, "ate eels");
620		ensure_message_contains(3, "buy iron");
621		ensure_message_contains(4, "bad word");
622		ensure_message_contains(5, "error");
623		ensure_message_contains(6, "big easy");
624		ensure_message_count(7);
625	}
626	
627	template<> template<>
628		// filtering by function, and that it will override class filtering
629	void ErrorTestObject::test<13>()
630	{
631		LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
632		LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN);
633		LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG);
634		LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE);
635		
636		TestBeta::doAll();
637		ensure_message_contains(0, "buy iron");
638		ensure_message_contains(1, "bad word");
639		ensure_message_count(2);
640	}
641	
642	template<> template<>
643		// filtering by file
644		// and that it is overridden by both class and function filtering
645	void ErrorTestObject::test<14>()
646	{
647		LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
648		LLError::setFileLevel(LLError::abbreviateFile(__FILE__),
649									LLError::LEVEL_WARN);
650		LLError::setClassLevel("TestAlpha", LLError::LEVEL_INFO);
651		LLError::setFunctionLevel("TestAlpha::doError",
652									LLError::LEVEL_NONE);
653		LLError::setFunctionLevel("TestBeta::doError",
654									LLError::LEVEL_NONE);
655		
656		TestAlpha::doAll();
657		TestBeta::doAll();
658		ensure_message_contains(0, "any idea");
659		ensure_message_contains(1, "aim west");
660		ensure_message_contains(2, "bad word");
661		ensure_message_count(3);
662	}
663	
664	template<> template<>
665		// proper cached, efficient lookup of filtering
666	void ErrorTestObject::test<15>()
667	{
668		LLError::setDefaultLevel(LLError::LEVEL_NONE);
669
670		TestAlpha::doInfo();
671		ensure_message_count(0);
672		ensure_equals("first check", LLError::shouldLogCallCount(), 1);
673		TestAlpha::doInfo();
674		ensure_message_count(0);
675		ensure_equals("second check", LLError::shouldLogCallCount(), 1);
676
677		LLError::setClassLevel("TestAlpha", LLError::LEVEL_DEBUG);
678		TestAlpha::doInfo();
679		ensure_message_count(1);
680		ensure_equals("third check", LLError::shouldLogCallCount(), 2);
681		TestAlpha::doInfo();
682		ensure_message_count(2);
683		ensure_equals("fourth check", LLError::shouldLogCallCount(), 2);
684
685		LLError::setClassLevel("TestAlpha", LLError::LEVEL_WARN);
686		TestAlpha::doInfo();
687		ensure_message_count(2);
688		ensure_equals("fifth check", LLError::shouldLogCallCount(), 3);
689		TestAlpha::doInfo();
690		ensure_message_count(2);
691		ensure_equals("sixth check", LLError::shouldLogCallCount(), 3);
692	}
693	
694	template<> template<>
695		// configuration from LLSD
696	void ErrorTestObject::test<16>()
697	{
698		std::string this_file = LLError::abbreviateFile(__FILE__);
699		LLSD config;
700		config["print-location"] = true;
701		config["default-level"] = "DEBUG";
702		
703		LLSD set1;
704		set1["level"] = "WARN";
705		set1["files"][0] = this_file;
706		
707		LLSD set2;
708		set2["level"] = "INFO";
709		set2["classes"][0] = "TestAlpha";
710		
711		LLSD set3;
712		set3["level"] = "NONE";
713		set3["functions"][0] = "TestAlpha::doError";
714		set3["functions"][1] = "TestBeta::doError";
715		
716		config["settings"][0] = set1;
717		config["settings"][1] = set2;
718		config["settings"][2] = set3;
719		
720		LLError::configure(config);
721		
722		TestAlpha::doAll();
723		TestBeta::doAll();
724		ensure_message_contains(0, "any idea");
725		ensure_message_contains(0, this_file);
726		ensure_message_contains(1, "aim west");
727		ensure_message_contains(2, "bad word");
728		ensure_message_count(3);
729		
730		// make sure reconfiguring works
731		LLSD config2;
732		config2["default-level"] = "WARN";
733		
734		LLError::configure(config2);
735		
736		TestAlpha::doAll();
737		TestBeta::doAll();
738		ensure_message_contains(3, "aim west");
739		ensure_message_does_not_contain(3, this_file);
740		ensure_message_contains(4, "error");
741		ensure_message_contains(5, "ate eels");
742		ensure_message_contains(6, "bad word");
743		ensure_message_contains(7, "error");
744		ensure_message_contains(8, "big easy");
745		ensure_message_count(9);
746	}
747}	
748
749/* Tests left:
750	handling of classes without LOG_CLASS
751
752	live update of filtering from file	
753	
754	syslog recorder
755	file recorder
756	cerr/stderr recorder
757	fixed buffer recorder
758	windows recorder
759
760	mutex use when logging (?)
761	strange careful about to crash handling (?)
762*/