00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #include <config.h>
00024
00025 #include "testsuite.h"
00026
00027 #include "backendmanager.h"
00028 #include "fdtracker.h"
00029 #include "testrunner.h"
00030 #include "safeunistd.h"
00031
00032 #ifdef HAVE_VALGRIND
00033 # include "safeerrno.h"
00034 # include <valgrind/memcheck.h>
00035 # include <sys/types.h>
00036 # include "safefcntl.h"
00037 #endif
00038
00039 #include <algorithm>
00040 #include <iostream>
00041 #include <set>
00042
00043 #include <cfloat>
00044 #include <cmath>
00045 #include <cstdio>
00046 #include <cstdlib>
00047 #include <cstring>
00048
00049 #include "gnu_getopt.h"
00050
00051 #include <setjmp.h>
00052 #include <signal.h>
00053
00054 #include <exception>
00055 #ifdef USE_RTTI
00056 # include <typeinfo>
00057 # ifdef __GNUC__
00058 # include <cxxabi.h>
00059 # endif
00060 #endif
00061
00062 #include <xapian/error.h>
00063 #include "noreturn.h"
00064 #include "stringutils.h"
00065 #include "utils.h"
00066
00067 using namespace std;
00068
00070 bool verbose;
00071
00072 #ifdef HAVE_VALGRIND
00073 static int vg_log_fd = -1;
00074 #endif
00075
00076 #ifdef HAVE_SIGSETJMP
00077 # define SIGSETJMP(ENV, SAVESIGS) sigsetjmp(ENV, SAVESIGS)
00078 # define SIGLONGJMP(ENV, VAL) siglongjmp(ENV, VAL)
00079 # define SIGJMP_BUF sigjmp_buf
00080 #else
00081 # define SIGSETJMP(ENV, SAVESIGS) setjmp(ENV)
00082 # define SIGLONGJMP(ENV, VAL) longjmp(ENV, VAL)
00083 # define SIGJMP_BUF jmp_buf
00084 #endif
00085
00087
00088
00089
00090 const char * expected_exception = NULL;
00091
00093 std::ostringstream tout;
00094
00095 int test_driver::runs = 0;
00096 test_driver::result test_driver::subtotal;
00097 test_driver::result test_driver::total;
00098 string test_driver::argv0;
00099 string test_driver::opt_help;
00100 map<int, string *> test_driver::short_opts;
00101 vector<string> test_driver::test_names;
00102 bool test_driver::abort_on_error = false;
00103 string test_driver::col_red, test_driver::col_green;
00104 string test_driver::col_yellow, test_driver::col_reset;
00105 bool test_driver::use_cr = false;
00106
00107 void
00108 test_driver::write_and_clear_tout()
00109 {
00110 const string & s = tout.str();
00111 if (!s.empty()) {
00112 out << '\n' << s;
00113 tout.str(string());
00114 }
00115 }
00116
00117 string
00118 test_driver::get_srcdir()
00119 {
00120 char *p = getenv("srcdir");
00121 if (p != NULL) return string(p);
00122
00123 #ifdef __WIN32__
00124
00125 const char ARGV0_SEP = '\\';
00126 #else
00127 const char ARGV0_SEP = '/';
00128 #endif
00129
00130 string srcdir(argv0);
00131 string::size_type i = srcdir.find_last_of(ARGV0_SEP);
00132 string srcfile;
00133 if (i != string::npos) {
00134 srcfile = srcdir.substr(i + 1);
00135 srcdir.erase(i);
00136
00137 i = srcdir.find_last_of(ARGV0_SEP);
00138 if (srcdir.substr(i + 1) == ".libs") {
00139 srcdir.erase(i);
00140
00141 if (startswith(srcfile, "lt-")) srcfile.erase(0, 3);
00142 }
00143 } else {
00144
00145
00146
00147 srcfile = srcdir;
00148 srcdir = ".";
00149 }
00150
00151
00152 if (endswith(srcfile, ".exe")) srcfile.resize(srcfile.size() - 4);
00153
00154
00155 if (!file_exists(srcdir + '/' + srcfile + ".cc")) {
00156 cout << argv0
00157 << ": srcdir is not in the environment and I can't guess it!\n"
00158 "Run test programs using the runtest script - see HACKING for details"
00159 << endl;
00160 exit(1);
00161 }
00162 return srcdir;
00163 }
00164
00165 test_driver::test_driver(const test_desc *tests_)
00166 : out(cout.rdbuf()), tests(tests_)
00167 {
00168 }
00169
00170 static SIGJMP_BUF jb;
00171 static int signum = 0;
00172 static void * sigaddr = NULL;
00173
00174
00175 extern "C" {
00176
00177 #if defined HAVE_SIGACTION && defined SA_SIGINFO
00178 XAPIAN_NORETURN(static void handle_sig(int signum_, siginfo_t *si, void *));
00179 static void handle_sig(int signum_, siginfo_t *si, void *)
00180 {
00181
00182
00183 struct sigaction sa;
00184 sa.sa_handler = SIG_DFL;
00185 sigemptyset(&sa.sa_mask);
00186 sa.sa_flags = 0;
00187
00188
00189 if (signum_ != SIGSEGV) sigaction(SIGSEGV, &sa, NULL);
00190 if (signum_ != SIGFPE) sigaction(SIGFPE, &sa, NULL);
00191 if (signum_ != SIGILL) sigaction(SIGILL, &sa, NULL);
00192 # ifdef SIGBUS
00193 if (signum_ != SIGBUS) sigaction(SIGBUS, &sa, NULL);
00194 # endif
00195 # ifdef SIGSTKFLT
00196 if (signum_ != SIGSTKFLT) sigaction(SIGSTKFLT, &sa, NULL);
00197 # endif
00198 signum = signum_;
00199 sigaddr = si->si_addr;
00200 SIGLONGJMP(jb, 1);
00201 }
00202
00203 #else
00204
00205 XAPIAN_NORETURN(static void handle_sig(int signum_));
00206 static void handle_sig(int signum_)
00207 {
00208
00209
00210 signal(SIGSEGV, SIG_DFL);
00211 signal(SIGFPE, SIG_DFL);
00212 signal(SIGILL, SIG_DFL);
00213 #ifdef SIGBUS
00214 signal(SIGBUS, SIG_DFL);
00215 #endif
00216 #ifdef SIGSTKFLT
00217 signal(SIGSTKFLT, SIG_DFL);
00218 #endif
00219 signum = signum_;
00220 SIGLONGJMP(jb, 1);
00221 }
00222 #endif
00223
00224 }
00225
00226 class SignalRedirector {
00227 private:
00228 bool active;
00229 public:
00230 SignalRedirector() : active(false) { }
00231 void activate() {
00232 active = true;
00233 signum = 0;
00234 sigaddr = NULL;
00235
00236 #if defined HAVE_SIGACTION && defined SA_SIGINFO
00237 struct sigaction sa;
00238 sa.sa_sigaction = handle_sig;
00239 sigemptyset(&sa.sa_mask);
00240 sa.sa_flags = SA_RESETHAND|SA_SIGINFO;
00241 sigaction(SIGSEGV, &sa, NULL);
00242 sigaction(SIGFPE, &sa, NULL);
00243 sigaction(SIGILL, &sa, NULL);
00244 # ifdef SIGBUS
00245 sigaction(SIGBUS, &sa, NULL);
00246 # endif
00247 # ifdef SIGSTKFLT
00248 sigaction(SIGSTKFLT, &sa, NULL);
00249 # endif
00250 #else
00251 signal(SIGSEGV, handle_sig);
00252 signal(SIGFPE, handle_sig);
00253 signal(SIGILL, handle_sig);
00254 # ifdef SIGBUS
00255 signal(SIGBUS, handle_sig);
00256 # endif
00257 # ifdef SIGSTKFLT
00258 signal(SIGSTKFLT, handle_sig);
00259 # endif
00260 #endif
00261 }
00262 ~SignalRedirector() {
00263 if (active) {
00264 #if defined HAVE_SIGACTION && defined SA_SIGINFO
00265 struct sigaction sa;
00266 sa.sa_handler = SIG_DFL;
00267 sigemptyset(&sa.sa_mask);
00268 sa.sa_flags = 0;
00269 sigaction(SIGSEGV, &sa, NULL);
00270 sigaction(SIGFPE, &sa, NULL);
00271 sigaction(SIGILL, &sa, NULL);
00272 # ifdef SIGBUS
00273 sigaction(SIGBUS, &sa, NULL);
00274 # endif
00275 # ifdef SIGSTKFLT
00276 sigaction(SIGSTKFLT, &sa, NULL);
00277 # endif
00278 #else
00279 signal(SIGSEGV, SIG_DFL);
00280 signal(SIGFPE, SIG_DFL);
00281 signal(SIGILL, SIG_DFL);
00282 # ifdef SIGBUS
00283 signal(SIGBUS, SIG_DFL);
00284 # endif
00285 # ifdef SIGSTKFLT
00286 signal(SIGSTKFLT, SIG_DFL);
00287 # endif
00288 #endif
00289 }
00290 }
00291 };
00292
00293
00294
00295
00296
00297
00298
00299 test_driver::test_result
00300 test_driver::runtest(const test_desc *test)
00301 {
00302
00303 volatile int runcount = 0;
00304
00305 FDTracker fdtracker;
00306 fdtracker.init();
00307
00308 while (true) {
00309 tout.str(string());
00310 if (SIGSETJMP(jb, 1) == 0) {
00311 SignalRedirector sig;
00312 static bool catch_signals =
00313 (getenv("XAPIAN_TESTSUITE_SIG_DFL") == NULL);
00314 if (catch_signals) sig.activate();
00315 try {
00316 expected_exception = NULL;
00317 #ifdef HAVE_VALGRIND
00318 int vg_errs = 0;
00319 long vg_leaks = 0, vg_dubious = 0, vg_reachable = 0;
00320 if (vg_log_fd != -1) {
00321 VALGRIND_DO_LEAK_CHECK;
00322 vg_errs = VALGRIND_COUNT_ERRORS;
00323 long dummy;
00324 VALGRIND_COUNT_LEAKS(vg_leaks, vg_dubious, vg_reachable, dummy);
00325 (void)dummy;
00326
00327 lseek(vg_log_fd, 0, SEEK_END);
00328 }
00329 #endif
00330 if (!test->run()) {
00331 out << col_red << " FAILED" << col_reset;
00332 write_and_clear_tout();
00333 return FAIL;
00334 }
00335 if (backendmanager)
00336 backendmanager->clean_up();
00337 #ifdef HAVE_VALGRIND
00338 if (vg_log_fd != -1) {
00339
00340
00341
00342
00343 tout.str(string());
00344 #define REPORT_FAIL_VG(M) do { \
00345 if (verbose) { \
00346 while (true) { \
00347 ssize_t c = read(vg_log_fd, buf, sizeof(buf)); \
00348 if (c == 0 || (c < 0 && errno != EINTR)) break; \
00349 if (c > 0) out << string(buf, c); \
00350 } \
00351 } \
00352 out << " " << col_red << M << col_reset; \
00353 } while (0)
00354
00355
00356 off_t curpos = lseek(vg_log_fd, 0, SEEK_CUR);
00357 char buf[4096];
00358 while (true) {
00359 ssize_t c = read(vg_log_fd, buf, sizeof(buf));
00360 if (c == 0 || (c < 0 && errno != EINTR)) {
00361 buf[0] = 0;
00362 break;
00363 }
00364 if (c > 0) {
00365
00366
00367
00368 ssize_t i = 0;
00369 while (true) {
00370 const char * spc;
00371 spc = static_cast<const char *>(
00372 memchr(buf + i, ' ', c - i));
00373 if (!spc) {
00374 i = c;
00375 break;
00376 }
00377 i = spc - buf;
00378 if (++i >= c) break;
00379 if (buf[i] == '\n')
00380 continue;
00381 if (c - i >= 8 &&
00382 (memcmp(buf + i, "Warning:", 8) == 0 ||
00383 memcmp(buf + i, " ", 3) == 0)) {
00384
00385 i += 3;
00386 const char * nl;
00387 nl = static_cast<const char *>(
00388 memchr(buf + i, '\n', c - i));
00389 if (!nl) {
00390 i = c;
00391 break;
00392 }
00393 i = nl - buf;
00394 continue;
00395 }
00396 break;
00397 }
00398
00399 char *start = buf + i;
00400 c -= i;
00401 if (c > 128) c = 128;
00402
00403 {
00404 const char *p;
00405 p = static_cast<const char*>(
00406 memchr(start, '\n', c));
00407 if (p != NULL) c = p - start;
00408 }
00409
00410 memmove(buf, start, c);
00411 buf[c] = '\0';
00412 break;
00413 }
00414 }
00415 lseek(vg_log_fd, curpos, SEEK_SET);
00416
00417 int vg_errs2 = VALGRIND_COUNT_ERRORS;
00418 vg_errs = vg_errs2 - vg_errs;
00419 VALGRIND_DO_LEAK_CHECK;
00420 long vg_leaks2 = 0, vg_dubious2 = 0, vg_reachable2 = 0;
00421 long dummy;
00422 VALGRIND_COUNT_LEAKS(vg_leaks2, vg_dubious2, vg_reachable2,
00423 dummy);
00424 (void)dummy;
00425 vg_leaks = vg_leaks2 - vg_leaks;
00426 vg_dubious = vg_dubious2 - vg_dubious;
00427 vg_reachable = vg_reachable2 - vg_reachable;
00428 if (vg_errs) {
00429 string fail_msg(buf);
00430 if (fail_msg.empty())
00431 fail_msg = "VALGRIND DETECTED A PROBLEM";
00432 REPORT_FAIL_VG(fail_msg);
00433 return FAIL;
00434 }
00435 if (vg_leaks > 0) {
00436 REPORT_FAIL_VG("LEAKED " << vg_leaks << " BYTES");
00437 return FAIL;
00438 }
00439 if (vg_dubious > 0) {
00440
00441
00442
00443
00444 if (runcount == 0) {
00445 out << col_yellow << " PROBABLY LEAKED MEMORY - RETRYING TEST" << col_reset;
00446 ++runcount;
00447
00448
00449 (void)fdtracker.check();
00450 continue;
00451 }
00452 REPORT_FAIL_VG("PROBABLY LEAKED " << vg_dubious << " BYTES");
00453 return FAIL;
00454 }
00455 if (vg_reachable > 0) {
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466
00467
00468 if (runcount == 0) {
00469 out << col_yellow << " POSSIBLE UNRELEASED MEMORY - RETRYING TEST" << col_reset;
00470 ++runcount;
00471
00472
00473 (void)fdtracker.check();
00474 continue;
00475 }
00476 REPORT_FAIL_VG("FAILED TO RELEASE " << vg_reachable << " BYTES");
00477 return FAIL;
00478 }
00479 }
00480 #endif
00481 if (!fdtracker.check()) {
00482 if (runcount == 0) {
00483 out << col_yellow << " POSSIBLE FDLEAK:" << fdtracker.get_message() << col_reset;
00484 ++runcount;
00485 continue;
00486 }
00487 out << col_red << " FDLEAK:" << fdtracker.get_message() << col_reset;
00488 return FAIL;
00489 }
00490 } catch (const TestFail &) {
00491 out << col_red << " FAILED" << col_reset;
00492 write_and_clear_tout();
00493 return FAIL;
00494 } catch (const TestSkip &) {
00495 out << col_yellow << " SKIPPED" << col_reset;
00496 write_and_clear_tout();
00497 return SKIP;
00498 } catch (const Xapian::Error &err) {
00499 string errclass = err.get_type();
00500 if (expected_exception && expected_exception == errclass) {
00501 out << col_yellow << " C++ FAILED TO CATCH " << errclass << col_reset;
00502 return SKIP;
00503 }
00504 out << " " << col_red << err.get_description() << col_reset;
00505 write_and_clear_tout();
00506 return FAIL;
00507 } catch (const string & msg) {
00508 out << col_red << " EXCEPTION std::string " << msg << col_reset;
00509 write_and_clear_tout();
00510 return FAIL;
00511 } catch (const std::exception & e) {
00512 out << " " << col_red;
00513 #ifndef USE_RTTI
00514 out << "std::exception";
00515 #else
00516 const char * name = typeid(e).name();
00517 # ifdef __GNUC__
00518
00519
00520 int status;
00521 char * realname = abi::__cxa_demangle(name, NULL, 0, &status);
00522 if (realname) {
00523 out << realname;
00524 free(realname);
00525 } else {
00526 out << name;
00527 }
00528 # else
00529 out << name;
00530 # endif
00531 #endif
00532 out << ": " << e.what() << col_reset;
00533 write_and_clear_tout();
00534 return FAIL;
00535 } catch (const char * msg) {
00536 out << col_red;
00537 if (msg) {
00538 out << " EXCEPTION char * " << msg;
00539 } else {
00540 out << " EXCEPTION (char*)NULL";
00541 }
00542 out << col_reset;
00543 write_and_clear_tout();
00544 return FAIL;
00545 } catch (...) {
00546 out << col_red << " UNKNOWN EXCEPTION" << col_reset;
00547 write_and_clear_tout();
00548 return FAIL;
00549 }
00550 return PASS;
00551 }
00552
00553
00554 const char *signame = "SIGNAL";
00555 bool show_addr = true;
00556 switch (signum) {
00557 case SIGSEGV: signame = "SIGSEGV"; break;
00558 case SIGFPE: signame = "SIGFPE"; break;
00559 case SIGILL: signame = "SIGILL"; break;
00560 #ifdef SIGBUS
00561 case SIGBUS: signame = "SIGBUS"; break;
00562 #endif
00563 #ifdef SIGSTKFLT
00564 case SIGSTKFLT:
00565 signame = "SIGSTKFLT";
00566 show_addr = false;
00567 break;
00568 #endif
00569 }
00570 out << " " << col_red << signame;
00571 if (show_addr) {
00572 char buf[40];
00573 sprintf(buf, " at %p", sigaddr);
00574 out << buf;
00575 }
00576 out << col_reset;
00577 write_and_clear_tout();
00578 return FAIL;
00579 }
00580 }
00581
00582 test_driver::result
00583 test_driver::run_tests(vector<string>::const_iterator b,
00584 vector<string>::const_iterator e)
00585 {
00586 return do_run_tests(b, e);
00587 }
00588
00589 test_driver::result
00590 test_driver::run_tests()
00591 {
00592 const vector<string> blank;
00593 return do_run_tests(blank.begin(), blank.end());
00594 }
00595
00596 test_driver::result
00597 test_driver::do_run_tests(vector<string>::const_iterator b,
00598 vector<string>::const_iterator e)
00599 {
00600 set<string> m(b, e);
00601 bool check_name = !m.empty();
00602
00603 test_driver::result res;
00604
00605 for (const test_desc *test = tests; test->name; test++) {
00606 bool do_this_test = !check_name;
00607 if (!do_this_test && m.find(test->name) != m.end())
00608 do_this_test = true;
00609 if (!do_this_test) {
00610
00611
00612 string t = test->name;
00613 string::size_type i;
00614 i = t.find_last_not_of("0123456789") + 1;
00615 if (i != string::npos) {
00616 t.resize(i);
00617 if (m.find(t) != m.end()) do_this_test = true;
00618 }
00619 }
00620 if (do_this_test) {
00621 out << "Running test: " << test->name << "...";
00622 out.flush();
00623 test_driver::test_result test_res = runtest(test);
00624 if (backendmanager)
00625 backendmanager->clean_up();
00626 switch (test_res) {
00627 case PASS:
00628 ++res.succeeded;
00629 if (verbose || !use_cr) {
00630 out << col_green << " ok" << col_reset << endl;
00631 } else {
00632 out << "\r \r";
00633 }
00634 break;
00635 case FAIL:
00636 ++res.failed;
00637 out << endl;
00638 if (abort_on_error) {
00639 throw "Test failed - aborting further tests";
00640 }
00641 break;
00642 case SKIP:
00643 ++res.skipped;
00644 out << endl;
00645
00646 break;
00647 }
00648 }
00649 }
00650 return res;
00651 }
00652
00653 void
00654 test_driver::usage()
00655 {
00656 cout << "Usage: " << argv0 << " [-v|--verbose] [-o|--abort-on-error] " << opt_help
00657 << "[TESTNAME]..." << endl;
00658 cout << " " << argv0 << " [-h|--help]" << endl;
00659 exit(1);
00660 }
00661
00662
00663 extern "C" {
00664
00665 static void
00666 report_totals(void)
00667 {
00668 test_driver::report(test_driver::total, "total");
00669 }
00670 }
00671
00672 void
00673 test_driver::report(const test_driver::result &r, const string &desc)
00674 {
00675
00676 if (++runs == 2) atexit(report_totals);
00677
00678 if (r.succeeded != 0 || r.failed != 0) {
00679 cout << argv0 << " " << desc << ": ";
00680
00681 if (r.failed == 0)
00682 cout << "All ";
00683
00684 cout << col_green << r.succeeded << col_reset << " tests passed";
00685
00686 if (r.failed != 0)
00687 cout << ", " << col_red << r.failed << col_reset << " failed";
00688
00689 if (r.skipped) {
00690 cout << ", " << col_yellow << r.skipped << col_reset
00691 << " skipped." << endl;
00692 } else {
00693 cout << "." << endl;
00694 }
00695 }
00696 }
00697
00698 void
00699 test_driver::add_command_line_option(const string &l, char s, string * arg)
00700 {
00701 short_opts.insert(make_pair<int, string *>(int(s), arg));
00702 opt_help += "[-";
00703 opt_help += s;
00704 opt_help += ' ';
00705 opt_help += l;
00706 opt_help += "] ";
00707 }
00708
00709 void
00710 test_driver::parse_command_line(int argc, char **argv)
00711 {
00712 argv0 = argv[0];
00713
00714 #ifndef __WIN32__
00715 bool colourise = true;
00716 const char *p = getenv("XAPIAN_TESTSUITE_OUTPUT");
00717 if (p == NULL || !*p || strcmp(p, "auto") == 0) {
00718 colourise = isatty(1);
00719 } else if (strcmp(p, "plain") == 0) {
00720 colourise = false;
00721 }
00722 if (colourise) {
00723 col_red = "\x1b[1m\x1b[31m";
00724 col_green = "\x1b[1m\x1b[32m";
00725 col_yellow = "\x1b[1m\x1b[33m";
00726 col_reset = "\x1b[0m";
00727 use_cr = true;
00728 }
00729 #endif
00730
00731 const struct option long_opts[] = {
00732 {"verbose", no_argument, 0, 'v'},
00733 {"abort-on-error", no_argument, 0, 'o'},
00734 {"help", no_argument, 0, 'h'},
00735 {NULL, 0, 0, 0}
00736 };
00737
00738 string short_opts_string = "voh";
00739 map<int, string *>::const_iterator i;
00740 for (i = short_opts.begin(); i != short_opts.end(); ++i) {
00741 short_opts_string += char(i->first);
00742 short_opts_string += ':';
00743 }
00744 const char * opts = short_opts_string.c_str();
00745
00746 int c;
00747 while ((c = gnu_getopt_long(argc, argv, opts, long_opts, 0)) != -1) {
00748 switch (c) {
00749 case 'v':
00750 verbose = true;
00751 break;
00752 case 'o':
00753 abort_on_error = true;
00754 break;
00755 default: {
00756 i = short_opts.find(c);
00757 if (i != short_opts.end()) {
00758 i->second->assign(optarg);
00759 break;
00760 }
00761
00762 usage();
00763 return;
00764 }
00765 }
00766 }
00767
00768 while (argv[optind]) {
00769 test_names.push_back(string(argv[optind]));
00770 optind++;
00771 }
00772
00773 #ifdef HAVE_VALGRIND
00774 if (RUNNING_ON_VALGRIND) {
00775 if (getenv("XAPIAN_TESTSUITE_VALGRIND") != NULL) {
00776
00777 char fname[64];
00778 sprintf(fname, ".valgrind.log.%lu", (unsigned long)getpid());
00779 vg_log_fd = open(fname, O_RDONLY|O_NONBLOCK);
00780 if (vg_log_fd != -1) unlink(fname);
00781 }
00782 }
00783 #endif
00784 }
00785
00786 int
00787 test_driver::run(const test_desc *tests)
00788 {
00789 test_driver driver(tests);
00790
00791 test_driver::result myresult;
00792 myresult = driver.run_tests(test_names.begin(), test_names.end());
00793
00794 subtotal += myresult;
00795
00796 return bool(myresult.failed);
00797 }
00798
00799 bool
00800 TEST_EQUAL_DOUBLE_(double a, double b)
00801 {
00802 if (a == b) return true;
00803 return (ceil(log10(max(fabs(a), fabs(b)))) - log10(fabs(a - b)) > DBL_DIG);
00804 }