OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2013 Google Inc. | 2 * Copyright 2013 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "CrashHandler.h" | 8 #include "CrashHandler.h" |
9 #include "DMJsonWriter.h" | 9 #include "DMJsonWriter.h" |
10 #include "DMSrcSink.h" | 10 #include "DMSrcSink.h" |
(...skipping 565 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
576 } | 576 } |
577 } | 577 } |
578 | 578 |
579 SkTArray<SkString> images; | 579 SkTArray<SkString> images; |
580 if (!CollectImages(&images)) { | 580 if (!CollectImages(&images)) { |
581 return false; | 581 return false; |
582 } | 582 } |
583 | 583 |
584 for (auto image : images) { | 584 for (auto image : images) { |
585 push_codec_srcs(image); | 585 push_codec_srcs(image); |
586 const char* ext = ""; | 586 const char* ext = strrchr(image.c_str(), '.'); |
587 int index = image.findLastOf('.'); | 587 if (ext && brd_supported(ext+1)) { |
588 if (index >= 0 && (size_t) ++index < image.size()) { | |
589 ext = &image.c_str()[index]; | |
590 } | |
591 if (brd_supported(ext)) { | |
592 push_brd_srcs(image); | 588 push_brd_srcs(image); |
593 } | 589 } |
594 } | 590 } |
595 | 591 |
596 return true; | 592 return true; |
597 } | 593 } |
598 | 594 |
599 static void push_sink(const SkCommandLineConfig& config, Sink* s) { | 595 static void push_sink(const SkCommandLineConfig& config, Sink* s) { |
600 SkAutoTDelete<Sink> sink(s); | 596 SkAutoTDelete<Sink> sink(s); |
601 | 597 |
(...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
835 // .png encoding are definitely thread safe. This lets us offload that work to
CPU threads. | 831 // .png encoding are definitely thread safe. This lets us offload that work to
CPU threads. |
836 static SkTaskGroup gDefinitelyThreadSafeWork; | 832 static SkTaskGroup gDefinitelyThreadSafeWork; |
837 | 833 |
838 // The finest-grained unit of work we can run: draw a single Src into a single S
ink, | 834 // The finest-grained unit of work we can run: draw a single Src into a single S
ink, |
839 // report any errors, and perhaps write out the output: a .png of the bitmap, or
a raw stream. | 835 // report any errors, and perhaps write out the output: a .png of the bitmap, or
a raw stream. |
840 struct Task { | 836 struct Task { |
841 Task(const TaggedSrc& src, const TaggedSink& sink) : src(src), sink(sink) {} | 837 Task(const TaggedSrc& src, const TaggedSink& sink) : src(src), sink(sink) {} |
842 const TaggedSrc& src; | 838 const TaggedSrc& src; |
843 const TaggedSink& sink; | 839 const TaggedSink& sink; |
844 | 840 |
845 static void Run(Task* task) { | 841 static void Run(const Task& task) { |
846 SkString name = task->src->name(); | 842 SkString name = task.src->name(); |
847 | 843 |
848 // We'll skip drawing this Src/Sink pair if: | 844 // We'll skip drawing this Src/Sink pair if: |
849 // - the Src vetoes the Sink; | 845 // - the Src vetoes the Sink; |
850 // - this Src / Sink combination is on the blacklist; | 846 // - this Src / Sink combination is on the blacklist; |
851 // - it's a dry run. | 847 // - it's a dry run. |
852 SkString note(task->src->veto(task->sink->flags()) ? " (veto)" : ""); | 848 SkString note(task.src->veto(task.sink->flags()) ? " (veto)" : ""); |
853 SkString whyBlacklisted = is_blacklisted(task->sink.tag.c_str(), task->s
rc.tag.c_str(), | 849 SkString whyBlacklisted = is_blacklisted(task.sink.tag.c_str(), task.src
.tag.c_str(), |
854 task->src.options.c_str(), name
.c_str()); | 850 task.src.options.c_str(), name.
c_str()); |
855 if (!whyBlacklisted.isEmpty()) { | 851 if (!whyBlacklisted.isEmpty()) { |
856 note.appendf(" (--blacklist %s)", whyBlacklisted.c_str()); | 852 note.appendf(" (--blacklist %s)", whyBlacklisted.c_str()); |
857 } | 853 } |
858 | 854 |
859 SkString log; | 855 SkString log; |
860 auto timerStart = now_ms(); | 856 auto timerStart = now_ms(); |
861 if (!FLAGS_dryRun && note.isEmpty()) { | 857 if (!FLAGS_dryRun && note.isEmpty()) { |
862 SkBitmap bitmap; | 858 SkBitmap bitmap; |
863 SkDynamicMemoryWStream stream; | 859 SkDynamicMemoryWStream stream; |
864 if (FLAGS_pre_log) { | 860 if (FLAGS_pre_log) { |
865 SkDebugf("\nRunning %s->%s", name.c_str(), task->sink.tag.c_str(
)); | 861 SkDebugf("\nRunning %s->%s", name.c_str(), task.sink.tag.c_str()
); |
866 } | 862 } |
867 start(task->sink.tag.c_str(), task->src.tag, task->src.options, name
.c_str()); | 863 start(task.sink.tag.c_str(), task.src.tag, task.src.options, name.c_
str()); |
868 Error err = task->sink->draw(*task->src, &bitmap, &stream, &log); | 864 Error err = task.sink->draw(*task.src, &bitmap, &stream, &log); |
869 if (!err.isEmpty()) { | 865 if (!err.isEmpty()) { |
870 if (err.isFatal()) { | 866 if (err.isFatal()) { |
871 fail(SkStringPrintf("%s %s %s %s: %s", | 867 fail(SkStringPrintf("%s %s %s %s: %s", |
872 task->sink.tag.c_str(), | 868 task.sink.tag.c_str(), |
873 task->src.tag.c_str(), | 869 task.src.tag.c_str(), |
874 task->src.options.c_str(), | 870 task.src.options.c_str(), |
875 name.c_str(), | 871 name.c_str(), |
876 err.c_str())); | 872 err.c_str())); |
877 } else { | 873 } else { |
878 note.appendf(" (skipped: %s)", err.c_str()); | 874 note.appendf(" (skipped: %s)", err.c_str()); |
879 auto elapsed = now_ms() - timerStart; | 875 auto elapsed = now_ms() - timerStart; |
880 done(elapsed, task->sink.tag.c_str(), task->src.tag, task->s
rc.options, | 876 done(elapsed, task.sink.tag.c_str(), task.src.tag, task.src.
options, |
881 name, note, log); | 877 name, note, log); |
882 return; | 878 return; |
883 } | 879 } |
884 } | 880 } |
885 | 881 |
886 // We're likely switching threads here, so we must capture by value,
[=] or [foo,bar]. | 882 // We're likely switching threads here, so we must capture by value,
[=] or [foo,bar]. |
887 SkStreamAsset* data = stream.detachAsStream(); | 883 SkStreamAsset* data = stream.detachAsStream(); |
888 gDefinitelyThreadSafeWork.add([task,name,bitmap,data]{ | 884 gDefinitelyThreadSafeWork.add([task,name,bitmap,data]{ |
889 SkAutoTDelete<SkStreamAsset> ownedData(data); | 885 SkAutoTDelete<SkStreamAsset> ownedData(data); |
890 | 886 |
(...skipping 20 matching lines...) Expand all Loading... |
911 } | 907 } |
912 } | 908 } |
913 SkMD5::Digest digest; | 909 SkMD5::Digest digest; |
914 hash.finish(digest); | 910 hash.finish(digest); |
915 for (int i = 0; i < 16; i++) { | 911 for (int i = 0; i < 16; i++) { |
916 md5.appendf("%02x", digest.data[i]); | 912 md5.appendf("%02x", digest.data[i]); |
917 } | 913 } |
918 } | 914 } |
919 | 915 |
920 if (!FLAGS_readPath.isEmpty() && | 916 if (!FLAGS_readPath.isEmpty() && |
921 !gGold.contains(Gold(task->sink.tag.c_str(), task->src.tag.c
_str(), | 917 !gGold.contains(Gold(task.sink.tag.c_str(), task.src.tag.c_s
tr(), |
922 task->src.options.c_str(), name, md5)))
{ | 918 task.src.options.c_str(), name, md5)))
{ |
923 fail(SkStringPrintf("%s not found for %s %s %s %s in %s", | 919 fail(SkStringPrintf("%s not found for %s %s %s %s in %s", |
924 md5.c_str(), | 920 md5.c_str(), |
925 task->sink.tag.c_str(), | 921 task.sink.tag.c_str(), |
926 task->src.tag.c_str(), | 922 task.src.tag.c_str(), |
927 task->src.options.c_str(), | 923 task.src.options.c_str(), |
928 name.c_str(), | 924 name.c_str(), |
929 FLAGS_readPath[0])); | 925 FLAGS_readPath[0])); |
930 } | 926 } |
931 | 927 |
932 if (!FLAGS_writePath.isEmpty()) { | 928 if (!FLAGS_writePath.isEmpty()) { |
933 const char* ext = task->sink->fileExtension(); | 929 const char* ext = task.sink->fileExtension(); |
934 if (data->getLength()) { | 930 if (data->getLength()) { |
935 WriteToDisk(*task, md5, ext, data, data->getLength(), nu
llptr); | 931 WriteToDisk(task, md5, ext, data, data->getLength(), nul
lptr); |
936 SkASSERT(bitmap.drawsNothing()); | 932 SkASSERT(bitmap.drawsNothing()); |
937 } else if (!bitmap.drawsNothing()) { | 933 } else if (!bitmap.drawsNothing()) { |
938 WriteToDisk(*task, md5, ext, nullptr, 0, &bitmap); | 934 WriteToDisk(task, md5, ext, nullptr, 0, &bitmap); |
939 } | 935 } |
940 } | 936 } |
941 }); | 937 }); |
942 } | 938 } |
943 auto elapsed = now_ms() - timerStart; | 939 auto elapsed = now_ms() - timerStart; |
944 done(elapsed, task->sink.tag.c_str(), task->src.tag.c_str(), task->src.o
ptions.c_str(), | 940 done(elapsed, task.sink.tag.c_str(), task.src.tag.c_str(), task.src.opti
ons.c_str(), |
945 name, note, log); | 941 name, note, log); |
946 } | 942 } |
947 | 943 |
948 static void WriteToDisk(const Task& task, | 944 static void WriteToDisk(const Task& task, |
949 SkString md5, | 945 SkString md5, |
950 const char* ext, | 946 const char* ext, |
951 SkStream* data, size_t len, | 947 SkStream* data, size_t len, |
952 const SkBitmap* bitmap) { | 948 const SkBitmap* bitmap) { |
953 JsonWriter::BitmapResult result; | 949 JsonWriter::BitmapResult result; |
954 result.name = task.src->name(); | 950 result.name = task.src->name(); |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1005 return; | 1001 return; |
1006 } | 1002 } |
1007 if (!file.writeStream(data, len)) { | 1003 if (!file.writeStream(data, len)) { |
1008 fail(SkStringPrintf("Can't write to %s.\n", path.c_str())); | 1004 fail(SkStringPrintf("Can't write to %s.\n", path.c_str())); |
1009 return; | 1005 return; |
1010 } | 1006 } |
1011 } | 1007 } |
1012 } | 1008 } |
1013 }; | 1009 }; |
1014 | 1010 |
1015 // Run all tasks in the same enclave serially on the same thread. | |
1016 // They can't possibly run concurrently with each other. | |
1017 static void run_enclave(SkTArray<Task>* tasks) { | |
1018 for (int i = 0; i < tasks->count(); i++) { | |
1019 Task::Run(tasks->begin() + i); | |
1020 } | |
1021 } | |
1022 | |
1023 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~*/ | 1011 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~*/ |
1024 | 1012 |
1025 // Unit tests don't fit so well into the Src/Sink model, so we give them special
treatment. | 1013 // Unit tests don't fit so well into the Src/Sink model, so we give them special
treatment. |
1026 | 1014 |
1027 static SkTDArray<skiatest::Test> gThreadedTests, gGPUTests; | 1015 static SkTDArray<skiatest::Test> gParallelTests, gSerialTests; |
1028 | 1016 |
1029 static void gather_tests() { | 1017 static void gather_tests() { |
1030 if (!FLAGS_src.contains("tests")) { | 1018 if (!FLAGS_src.contains("tests")) { |
1031 return; | 1019 return; |
1032 } | 1020 } |
1033 for (const skiatest::TestRegistry* r = skiatest::TestRegistry::Head(); r; r
= r->next()) { | 1021 for (const skiatest::TestRegistry* r = skiatest::TestRegistry::Head(); r; r
= r->next()) { |
1034 if (!in_shard()) { | 1022 if (!in_shard()) { |
1035 continue; | 1023 continue; |
1036 } | 1024 } |
1037 // Despite its name, factory() is returning a reference to | 1025 // Despite its name, factory() is returning a reference to |
1038 // link-time static const POD data. | 1026 // link-time static const POD data. |
1039 const skiatest::Test& test = r->factory(); | 1027 const skiatest::Test& test = r->factory(); |
1040 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, test.name)) { | 1028 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, test.name)) { |
1041 continue; | 1029 continue; |
1042 } | 1030 } |
1043 if (test.needsGpu && gpu_supported()) { | 1031 if (test.needsGpu && gpu_supported()) { |
1044 (FLAGS_gpu_threading ? gThreadedTests : gGPUTests).push(test); | 1032 (FLAGS_gpu_threading ? gParallelTests : gSerialTests).push(test); |
1045 } else if (!test.needsGpu && FLAGS_cpu) { | 1033 } else if (!test.needsGpu && FLAGS_cpu) { |
1046 gThreadedTests.push(test); | 1034 gParallelTests.push(test); |
1047 } | 1035 } |
1048 } | 1036 } |
1049 } | 1037 } |
1050 | 1038 |
1051 static void run_test(skiatest::Test* test) { | 1039 static void run_test(skiatest::Test test) { |
1052 struct : public skiatest::Reporter { | 1040 struct : public skiatest::Reporter { |
1053 void reportFailed(const skiatest::Failure& failure) override { | 1041 void reportFailed(const skiatest::Failure& failure) override { |
1054 fail(failure.toString()); | 1042 fail(failure.toString()); |
1055 JsonWriter::AddTestFailure(failure); | 1043 JsonWriter::AddTestFailure(failure); |
1056 } | 1044 } |
1057 bool allowExtendedTest() const override { | 1045 bool allowExtendedTest() const override { |
1058 return FLAGS_pathOpsExtended; | 1046 return FLAGS_pathOpsExtended; |
1059 } | 1047 } |
1060 bool verbose() const override { return FLAGS_veryVerbose; } | 1048 bool verbose() const override { return FLAGS_veryVerbose; } |
1061 } reporter; | 1049 } reporter; |
1062 | 1050 |
1063 SkString note; | 1051 SkString note; |
1064 SkString whyBlacklisted = is_blacklisted("_", "tests", "_", test->name); | 1052 SkString whyBlacklisted = is_blacklisted("_", "tests", "_", test.name); |
1065 if (!whyBlacklisted.isEmpty()) { | 1053 if (!whyBlacklisted.isEmpty()) { |
1066 note.appendf(" (--blacklist %s)", whyBlacklisted.c_str()); | 1054 note.appendf(" (--blacklist %s)", whyBlacklisted.c_str()); |
1067 } | 1055 } |
1068 | 1056 |
1069 auto timerStart = now_ms(); | 1057 auto timerStart = now_ms(); |
1070 if (!FLAGS_dryRun && whyBlacklisted.isEmpty()) { | 1058 if (!FLAGS_dryRun && whyBlacklisted.isEmpty()) { |
1071 start("unit", "test", "", test->name); | 1059 start("unit", "test", "", test.name); |
1072 GrContextFactory factory; | 1060 GrContextFactory factory; |
1073 if (FLAGS_pre_log) { | 1061 if (FLAGS_pre_log) { |
1074 SkDebugf("\nRunning test %s", test->name); | 1062 SkDebugf("\nRunning test %s", test.name); |
1075 } | 1063 } |
1076 test->proc(&reporter, &factory); | 1064 test.proc(&reporter, &factory); |
1077 } | 1065 } |
1078 done(now_ms()-timerStart, "unit", "test", "", test->name, note, ""); | 1066 done(now_ms()-timerStart, "unit", "test", "", test.name, note, ""); |
1079 } | 1067 } |
1080 | 1068 |
1081 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~*/ | 1069 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~*/ |
1082 | 1070 |
1083 // If we're isolating all GPU-bound work to one thread (the default), this funct
ion runs all that. | |
1084 static void run_enclave_and_gpu_tests(SkTArray<Task>* tasks) { | |
1085 run_enclave(tasks); | |
1086 for (int i = 0; i < gGPUTests.count(); i++) { | |
1087 run_test(&gGPUTests[i]); | |
1088 } | |
1089 } | |
1090 | |
1091 // Some runs (mostly, Valgrind) are so slow that the bot framework thinks we've
hung. | 1071 // Some runs (mostly, Valgrind) are so slow that the bot framework thinks we've
hung. |
1092 // This prints something every once in a while so that it knows we're still work
ing. | 1072 // This prints something every once in a while so that it knows we're still work
ing. |
1093 static void start_keepalive() { | 1073 static void start_keepalive() { |
1094 struct Loop { | 1074 struct Loop { |
1095 static void forever(void*) { | 1075 static void forever(void*) { |
1096 for (;;) { | 1076 for (;;) { |
1097 static const int kSec = 300; | 1077 static const int kSec = 300; |
1098 #if defined(SK_BUILD_FOR_WIN) | 1078 #if defined(SK_BUILD_FOR_WIN) |
1099 Sleep(kSec * 1000); | 1079 Sleep(kSec * 1000); |
1100 #else | 1080 #else |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1141 | 1121 |
1142 gather_gold(); | 1122 gather_gold(); |
1143 gather_uninteresting_hashes(); | 1123 gather_uninteresting_hashes(); |
1144 | 1124 |
1145 if (!gather_srcs()) { | 1125 if (!gather_srcs()) { |
1146 return 1; | 1126 return 1; |
1147 } | 1127 } |
1148 gather_sinks(); | 1128 gather_sinks(); |
1149 gather_tests(); | 1129 gather_tests(); |
1150 | 1130 |
1151 gPending = gSrcs.count() * gSinks.count() + gThreadedTests.count() + gGPUTes
ts.count(); | 1131 gPending = gSrcs.count() * gSinks.count() + gParallelTests.count() + gSerial
Tests.count(); |
1152 SkDebugf("%d srcs * %d sinks + %d tests == %d tasks\n", | 1132 SkDebugf("%d srcs * %d sinks + %d tests == %d tasks\n", |
1153 gSrcs.count(), gSinks.count(), gThreadedTests.count() + gGPUTests.c
ount(), gPending); | 1133 gSrcs.count(), gSinks.count(), gParallelTests.count() + gSerialTest
s.count(), gPending); |
1154 | 1134 |
1155 // We try to exploit as much parallelism as is safe. Most Src/Sink pairs ru
n on any thread, | 1135 // Kick off as much parallel work as we can, making note of any serial work
we'll need to do. |
1156 // but Sinks that identify as part of a particular enclave run serially on a
single thread. | 1136 SkTaskGroup parallel; |
1157 // CPU tests run on any thread. GPU tests depend on --gpu_threading. | 1137 SkTArray<Task> serial; |
1158 SkTArray<Task> enclaves[kNumEnclaves]; | 1138 |
1159 for (int j = 0; j < gSinks.count(); j++) { | 1139 for (auto& sink : gSinks) |
1160 SkTArray<Task>& tasks = enclaves[gSinks[j]->enclave()]; | 1140 for (auto& src : gSrcs) { |
1161 for (int i = 0; i < gSrcs.count(); i++) { | 1141 Task task(src, sink); |
1162 tasks.push_back(Task(gSrcs[i], gSinks[j])); | 1142 if (src->serial() || sink->serial()) { |
| 1143 serial.push_back(task); |
| 1144 } else { |
| 1145 parallel.add([task] { Task::Run(task); }); |
1163 } | 1146 } |
1164 } | 1147 } |
| 1148 for (auto test : gParallelTests) { |
| 1149 parallel.add([test] { run_test(test); }); |
| 1150 } |
1165 | 1151 |
1166 SkTaskGroup tg; | 1152 // With the parallel work running, run serial tasks and tests here on main t
hread. |
1167 tg.batch(gThreadedTests.count(), [](int i){ run_test(&gThreadedTests[i]); })
; | 1153 for (auto task : serial) { Task::Run(task); } |
1168 for (int i = 0; i < kNumEnclaves; i++) { | 1154 for (auto test : gSerialTests) { run_test(test); } |
1169 SkTArray<Task>* currentEnclave = &enclaves[i]; | 1155 |
1170 switch(i) { | 1156 // Wait for any remaining parallel work to complete (including any spun off
of serial tasks). |
1171 case kAnyThread_Enclave: | 1157 parallel.wait(); |
1172 tg.batch(currentEnclave->count(), | |
1173 [currentEnclave](int j) { Task::Run(&(*currentEnclave)[
j]); }); | |
1174 break; | |
1175 case kGPU_Enclave: | |
1176 tg.add([currentEnclave](){ run_enclave_and_gpu_tests(currentEncl
ave); }); | |
1177 break; | |
1178 default: | |
1179 tg.add([currentEnclave](){ run_enclave(currentEnclave); }); | |
1180 break; | |
1181 } | |
1182 } | |
1183 tg.wait(); | |
1184 gDefinitelyThreadSafeWork.wait(); | 1158 gDefinitelyThreadSafeWork.wait(); |
1185 | 1159 |
1186 // At this point we're back in single-threaded land. | 1160 // At this point we're back in single-threaded land. |
1187 sk_tool_utils::release_portable_typefaces(); | 1161 sk_tool_utils::release_portable_typefaces(); |
1188 | 1162 |
1189 if (FLAGS_verbose && gNoteTally.count() > 0) { | 1163 if (FLAGS_verbose && gNoteTally.count() > 0) { |
1190 SkDebugf("\nNote tally:\n"); | 1164 SkDebugf("\nNote tally:\n"); |
1191 gNoteTally.foreach([](const SkString& note, int* tally) { | 1165 gNoteTally.foreach([](const SkString& note, int* tally) { |
1192 SkDebugf("%dx\t%s\n", *tally, note.c_str()); | 1166 SkDebugf("%dx\t%s\n", *tally, note.c_str()); |
1193 }); | 1167 }); |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1300 Reporter* reporter, | 1274 Reporter* reporter, |
1301 GrContextFactory* fac
tory); | 1275 GrContextFactory* fac
tory); |
1302 } // namespace skiatest | 1276 } // namespace skiatest |
1303 | 1277 |
1304 #if !defined(SK_BUILD_FOR_IOS) | 1278 #if !defined(SK_BUILD_FOR_IOS) |
1305 int main(int argc, char** argv) { | 1279 int main(int argc, char** argv) { |
1306 SkCommandLineFlags::Parse(argc, argv); | 1280 SkCommandLineFlags::Parse(argc, argv); |
1307 return dm_main(); | 1281 return dm_main(); |
1308 } | 1282 } |
1309 #endif | 1283 #endif |
OLD | NEW |