ktest.pl 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. #!/usr/bin/perl -w
  2. use strict;
  3. use IPC::Open2;
  4. use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
  5. use FileHandle;
  6. $#ARGV >= 0 || die "usage: autotest.pl config-file\n";
  7. $| = 1;
  8. my %opt;
  9. #default opts
  10. $opt{"NUM_BUILDS"} = 5;
  11. $opt{"DEFAULT_BUILD_TYPE"} = "randconfig";
  12. $opt{"MAKE_CMD"} = "make";
  13. $opt{"TIMEOUT"} = 50;
  14. $opt{"TMP_DIR"} = "/tmp/autotest";
  15. $opt{"SLEEP_TIME"} = 60; # sleep time between tests
  16. $opt{"BUILD_NOCLEAN"} = 0;
  17. $opt{"REBOOT_ON_ERROR"} = 0;
  18. $opt{"POWEROFF_ON_ERROR"} = 0;
  19. $opt{"POWEROFF_ON_SUCCESS"} = 0;
  20. $opt{"BUILD_OPTIONS"} = "";
  21. $opt{"BISECT_SLEEP_TIME"} = 10; # sleep time between bisects
  22. my $version;
  23. my $grub_number;
  24. my $target;
  25. my $make;
  26. my $noclean;
  27. my $minconfig;
  28. my $in_bisect = 0;
  29. my $bisect_bad = "";
  30. my $run_test;
  31. sub read_config {
  32. my ($config) = @_;
  33. open(IN, $config) || die "can't read file $config";
  34. while (<IN>) {
  35. # ignore blank lines and comments
  36. next if (/^\s*$/ || /\s*\#/);
  37. if (/^\s*(\S+)\s*=\s*(.*?)\s*$/) {
  38. my $lvalue = $1;
  39. my $rvalue = $2;
  40. $opt{$lvalue} = $rvalue;
  41. }
  42. }
  43. close(IN);
  44. }
  45. sub logit {
  46. if (defined($opt{"LOG_FILE"})) {
  47. open(OUT, ">> $opt{LOG_FILE}") or die "Can't write to $opt{LOG_FILE}";
  48. print OUT @_;
  49. close(OUT);
  50. }
  51. }
  52. sub doprint {
  53. print @_;
  54. logit @_;
  55. }
  56. sub dodie {
  57. doprint "CRITICAL FAILURE... ", @_, "\n";
  58. if ($opt{"REBOOT_ON_ERROR"}) {
  59. doprint "REBOOTING\n";
  60. `$opt{"POWER_CYCLE"}`;
  61. } elsif ($opt{"POWEROFF_ON_ERROR"} && defined($opt{"POWER_OFF"})) {
  62. doprint "POWERING OFF\n";
  63. `$opt{"POWER_OFF"}`;
  64. }
  65. die @_;
  66. }
  67. sub run_command {
  68. my ($command) = @_;
  69. my $redirect_log = "";
  70. if (defined($opt{"LOG_FILE"})) {
  71. $redirect_log = " >> $opt{LOG_FILE} 2>&1";
  72. }
  73. doprint "$command ... ";
  74. `$command $redirect_log`;
  75. my $failed = $?;
  76. if ($failed) {
  77. doprint "FAILED!\n";
  78. } else {
  79. doprint "SUCCESS\n";
  80. }
  81. return !$failed;
  82. }
  83. sub get_grub_index {
  84. return if (defined($grub_number));
  85. doprint "Find grub menu ... ";
  86. $grub_number = -1;
  87. open(IN, "ssh $target cat /boot/grub/menu.lst |")
  88. or die "unable to get menu.lst";
  89. while (<IN>) {
  90. if (/^\s*title\s+$opt{GRUB_MENU}\s*$/) {
  91. $grub_number++;
  92. last;
  93. } elsif (/^\s*title\s/) {
  94. $grub_number++;
  95. }
  96. }
  97. close(IN);
  98. die "Could not find '$opt{GRUB_MENU}' in /boot/grub/menu on $opt{MACHINE}"
  99. if ($grub_number < 0);
  100. doprint "$grub_number\n";
  101. }
  102. my $timeout = $opt{"TIMEOUT"};
  103. sub wait_for_input
  104. {
  105. my ($fp, $time) = @_;
  106. my $rin;
  107. my $ready;
  108. my $line;
  109. my $ch;
  110. if (!defined($time)) {
  111. $time = $timeout;
  112. }
  113. $rin = '';
  114. vec($rin, fileno($fp), 1) = 1;
  115. $ready = select($rin, undef, undef, $time);
  116. $line = "";
  117. # try to read one char at a time
  118. while (sysread $fp, $ch, 1) {
  119. $line .= $ch;
  120. last if ($ch eq "\n");
  121. }
  122. if (!length($line)) {
  123. return undef;
  124. }
  125. return $line;
  126. }
  127. sub reboot_to {
  128. run_command "ssh $target '(echo \"savedefault --default=$grub_number --once\" | grub --batch; reboot)'";
  129. }
  130. sub open_console {
  131. my ($fp) = @_;
  132. my $flags;
  133. my $pid = open($fp, "$opt{CONSOLE}|") or
  134. dodie "Can't open console $opt{CONSOLE}";
  135. $flags = fcntl($fp, F_GETFL, 0) or
  136. dodie "Can't get flags for the socket: $!\n";
  137. $flags = fcntl($fp, F_SETFL, $flags | O_NONBLOCK) or
  138. dodie "Can't set flags for the socket: $!\n";
  139. return $pid;
  140. }
  141. sub close_console {
  142. my ($fp, $pid) = @_;
  143. doprint "kill child process $pid\n";
  144. kill 2, $pid;
  145. print "closing!\n";
  146. close($fp);
  147. }
  148. sub monitor {
  149. my $booted = 0;
  150. my $bug = 0;
  151. my $pid;
  152. my $skip_call_trace = 0;
  153. my $fp = \*IN;
  154. $pid = open_console($fp);
  155. my $line;
  156. my $full_line = "";
  157. doprint "Wait for monitor to settle down.\n";
  158. # read the monitor and wait for the system to calm down
  159. do {
  160. $line = wait_for_input($fp, 5);
  161. } while (defined($line));
  162. reboot_to;
  163. for (;;) {
  164. $line = wait_for_input($fp);
  165. last if (!defined($line));
  166. doprint $line;
  167. # we are not guaranteed to get a full line
  168. $full_line .= $line;
  169. if ($full_line =~ /login:/) {
  170. $booted = 1;
  171. }
  172. if ($full_line =~ /\[ backtrace testing \]/) {
  173. $skip_call_trace = 1;
  174. }
  175. if ($full_line =~ /call trace:/i) {
  176. $bug = 1 if (!$skip_call_trace);
  177. }
  178. if ($full_line =~ /\[ end of backtrace testing \]/) {
  179. $skip_call_trace = 0;
  180. }
  181. if ($full_line =~ /Kernel panic -/) {
  182. $bug = 1;
  183. }
  184. if ($line =~ /\n/) {
  185. $full_line = "";
  186. }
  187. }
  188. close_console($fp, $pid);
  189. if (!$booted) {
  190. return 1 if ($in_bisect);
  191. dodie "failed - never got a boot prompt.\n";
  192. }
  193. if ($bug) {
  194. return 1 if ($in_bisect);
  195. dodie "failed - got a bug report\n";
  196. }
  197. return 0;
  198. }
  199. sub install {
  200. run_command "scp $opt{OUTPUT_DIR}/$opt{BUILD_TARGET} $target:$opt{TARGET_IMAGE}" or
  201. dodie "failed to copy image";
  202. my $install_mods = 0;
  203. # should we process modules?
  204. $install_mods = 0;
  205. open(IN, "$opt{OUTPUT_DIR}/.config") or dodie("Can't read config file");
  206. while (<IN>) {
  207. if (/CONFIG_MODULES(=y)?/) {
  208. $install_mods = 1 if (defined($1));
  209. last;
  210. }
  211. }
  212. close(IN);
  213. if (!$install_mods) {
  214. doprint "No modules needed\n";
  215. return;
  216. }
  217. run_command "$make INSTALL_MOD_PATH=$opt{TMP_DIR} modules_install" or
  218. dodie "Failed to install modules";
  219. my $modlib = "/lib/modules/$version";
  220. my $modtar = "autotest-mods.tar.bz2";
  221. run_command "ssh $target rm -rf $modlib" or
  222. dodie "failed to remove old mods: $modlib";
  223. # would be nice if scp -r did not follow symbolic links
  224. run_command "cd $opt{TMP_DIR} && tar -cjf $modtar lib/modules/$version" or
  225. dodie "making tarball";
  226. run_command "scp $opt{TMP_DIR}/$modtar $target:/tmp" or
  227. dodie "failed to copy modules";
  228. unlink "$opt{TMP_DIR}/$modtar";
  229. run_command "ssh $target '(cd / && tar xf /tmp/$modtar)'" or
  230. dodie "failed to tar modules";
  231. run_command "ssh $target rm -f /tmp/$modtar";
  232. }
  233. sub build {
  234. my ($type) = @_;
  235. my $defconfig = "";
  236. my $append = "";
  237. if ($type =~ /^useconfig:(.*)/) {
  238. run_command "cp $1 $opt{OUTPUT_DIR}/.config" or
  239. dodie "could not copy $1 to .config";
  240. $type = "oldconfig";
  241. }
  242. # old config can ask questions
  243. if ($type eq "oldconfig") {
  244. $append = "yes ''|";
  245. # allow for empty configs
  246. run_command "touch $opt{OUTPUT_DIR}/.config";
  247. run_command "mv $opt{OUTPUT_DIR}/.config $opt{OUTPUT_DIR}/config_temp" or
  248. dodie "moving .config";
  249. if (!$noclean && !run_command "$make mrproper") {
  250. dodie "make mrproper";
  251. }
  252. run_command "mv $opt{OUTPUT_DIR}/config_temp $opt{OUTPUT_DIR}/.config" or
  253. dodie "moving config_temp";
  254. } elsif (!$noclean) {
  255. unlink "$opt{OUTPUT_DIR}/.config";
  256. run_command "$make mrproper" or
  257. dodie "make mrproper";
  258. }
  259. # add something to distinguish this build
  260. open(OUT, "> $opt{OUTPUT_DIR}/localversion") or dodie("Can't make localversion file");
  261. print OUT "$opt{LOCALVERSION}\n";
  262. close(OUT);
  263. if (defined($minconfig)) {
  264. $defconfig = "KCONFIG_ALLCONFIG=$minconfig";
  265. }
  266. run_command "$defconfig $append $make $type" or
  267. dodie "failed make config";
  268. if (!run_command "$make $opt{BUILD_OPTIONS}") {
  269. # bisect may need this to pass
  270. return 1 if ($in_bisect);
  271. dodie "failed build";
  272. }
  273. return 0;
  274. }
  275. sub reboot {
  276. # try to reboot normally
  277. if (!run_command "ssh $target reboot") {
  278. # nope? power cycle it.
  279. run_command "$opt{POWER_CYCLE}";
  280. }
  281. }
  282. sub halt {
  283. if (!run_command "ssh $target halt" or defined($opt{"POWER_OFF"})) {
  284. # nope? the zap it!
  285. run_command "$opt{POWER_OFF}";
  286. }
  287. }
  288. sub success {
  289. my ($i) = @_;
  290. doprint "\n\n*******************************************\n";
  291. doprint "*******************************************\n";
  292. doprint "** SUCCESS!!!! **\n";
  293. doprint "*******************************************\n";
  294. doprint "*******************************************\n";
  295. if ($i != $opt{"NUM_BUILDS"}) {
  296. reboot;
  297. doprint "Sleeping $opt{SLEEP_TIME} seconds\n";
  298. sleep "$opt{SLEEP_TIME}";
  299. }
  300. }
  301. sub get_version {
  302. # get the release name
  303. doprint "$make kernelrelease ... ";
  304. $version = `$make kernelrelease | tail -1`;
  305. chomp($version);
  306. doprint "$version\n";
  307. }
  308. sub child_run_test {
  309. my $failed;
  310. $failed = !run_command $run_test;
  311. exit $failed;
  312. }
  313. my $child_done;
  314. sub child_finished {
  315. $child_done = 1;
  316. }
  317. sub do_run_test {
  318. my $child_pid;
  319. my $child_exit;
  320. my $pid;
  321. my $line;
  322. my $full_line;
  323. my $bug = 0;
  324. my $fp = \*IN;
  325. $pid = open_console($fp);
  326. # read the monitor and wait for the system to calm down
  327. do {
  328. $line = wait_for_input($fp, 1);
  329. } while (defined($line));
  330. $child_done = 0;
  331. $SIG{CHLD} = qw(child_finished);
  332. $child_pid = fork;
  333. child_run_test if (!$child_pid);
  334. $full_line = "";
  335. do {
  336. $line = wait_for_input($fp, 1);
  337. if (defined($line)) {
  338. # we are not guaranteed to get a full line
  339. $full_line .= $line;
  340. if ($full_line =~ /call trace:/i) {
  341. $bug = 1;
  342. }
  343. if ($full_line =~ /Kernel panic -/) {
  344. $bug = 1;
  345. }
  346. if ($line =~ /\n/) {
  347. $full_line = "";
  348. }
  349. }
  350. } while (!$child_done && !$bug);
  351. if ($bug) {
  352. doprint "Detected kernel crash!\n";
  353. # kill the child with extreme prejudice
  354. kill 9, $child_pid;
  355. }
  356. waitpid $child_pid, 0;
  357. $child_exit = $?;
  358. close_console($fp, $pid);
  359. if ($bug || $child_exit) {
  360. return 1 if $in_bisect;
  361. dodie "test failed";
  362. }
  363. return 0;
  364. }
  365. sub run_bisect {
  366. my ($type) = @_;
  367. my $failed;
  368. my $result;
  369. my $output;
  370. my $ret;
  371. if (defined($minconfig)) {
  372. $failed = build "useconfig:$minconfig";
  373. } else {
  374. # ?? no config to use?
  375. $failed = build "oldconfig";
  376. }
  377. if ($type ne "build") {
  378. dodie "Failed on build" if $failed;
  379. # Now boot the box
  380. get_grub_index;
  381. get_version;
  382. install;
  383. $failed = monitor;
  384. if ($type ne "boot") {
  385. dodie "Failed on boot" if $failed;
  386. $failed = do_run_test;
  387. }
  388. }
  389. if ($failed) {
  390. $result = "bad";
  391. # reboot the box to a good kernel
  392. if ($type eq "boot") {
  393. reboot;
  394. doprint "sleep a little for reboot\n";
  395. sleep $opt{"BISECT_SLEEP_TIME"};
  396. }
  397. } else {
  398. $result = "good";
  399. }
  400. doprint "git bisect $result ... ";
  401. $output = `git bisect $result 2>&1`;
  402. $ret = $?;
  403. logit $output;
  404. if ($ret) {
  405. doprint "FAILED\n";
  406. dodie "Failed to git bisect";
  407. }
  408. doprint "SUCCESS\n";
  409. if ($output =~ m/^(Bisecting: .*\(roughly \d+ steps?\))\s+\[([[:xdigit:]]+)\]/) {
  410. doprint "$1 [$2]\n";
  411. } elsif ($output =~ m/^([[:xdigit:]]+) is the first bad commit/) {
  412. $bisect_bad = $1;
  413. doprint "Found bad commit... $1\n";
  414. return 0;
  415. } else {
  416. # we already logged it, just print it now.
  417. print $output;
  418. }
  419. return 1;
  420. }
  421. sub bisect {
  422. my ($i) = @_;
  423. my $result;
  424. die "BISECT_GOOD[$i] not defined\n" if (!defined($opt{"BISECT_GOOD[$i]"}));
  425. die "BISECT_BAD[$i] not defined\n" if (!defined($opt{"BISECT_BAD[$i]"}));
  426. die "BISECT_TYPE[$i] not defined\n" if (!defined($opt{"BISECT_TYPE[$i]"}));
  427. my $good = $opt{"BISECT_GOOD[$i]"};
  428. my $bad = $opt{"BISECT_BAD[$i]"};
  429. my $type = $opt{"BISECT_TYPE[$i]"};
  430. $in_bisect = 1;
  431. run_command "git bisect start" or
  432. dodie "could not start bisect";
  433. run_command "git bisect good $good" or
  434. dodie "could not set bisect good to $good";
  435. run_command "git bisect bad $bad" or
  436. dodie "could not set bisect good to $bad";
  437. # Can't have a test without having a test to run
  438. if ($type eq "test" && !defined($run_test)) {
  439. $type = "boot";
  440. }
  441. do {
  442. $result = run_bisect $type;
  443. } while ($result);
  444. run_command "git bisect log" or
  445. dodie "could not capture git bisect log";
  446. run_command "git bisect reset" or
  447. dodie "could not reset git bisect";
  448. doprint "Bad commit was [$bisect_bad]\n";
  449. $in_bisect = 0;
  450. success $i;
  451. }
  452. read_config $ARGV[0];
  453. # mandatory configs
  454. die "MACHINE not defined\n" if (!defined($opt{"MACHINE"}));
  455. die "SSH_USER not defined\n" if (!defined($opt{"SSH_USER"}));
  456. die "BUILD_DIR not defined\n" if (!defined($opt{"BUILD_DIR"}));
  457. die "OUTPUT_DIR not defined\n" if (!defined($opt{"OUTPUT_DIR"}));
  458. die "BUILD_TARGET not defined\n" if (!defined($opt{"BUILD_TARGET"}));
  459. die "TARGET_IMAGE not defined\n" if (!defined($opt{"TARGET_IMAGE"}));
  460. die "POWER_CYCLE not defined\n" if (!defined($opt{"POWER_CYCLE"}));
  461. die "CONSOLE not defined\n" if (!defined($opt{"CONSOLE"}));
  462. die "LOCALVERSION not defined\n" if (!defined($opt{"LOCALVERSION"}));
  463. die "GRUB_MENU not defined\n" if (!defined($opt{"GRUB_MENU"}));
  464. chdir $opt{"BUILD_DIR"} || die "can't change directory to $opt{BUILD_DIR}";
  465. $target = "$opt{SSH_USER}\@$opt{MACHINE}";
  466. doprint "\n\nSTARTING AUTOMATED TESTS\n";
  467. $make = "$opt{MAKE_CMD} O=$opt{OUTPUT_DIR}";
  468. sub set_build_option {
  469. my ($name, $i) = @_;
  470. my $option = "$name\[$i\]";
  471. if (defined($opt{$option})) {
  472. return $opt{$option};
  473. }
  474. if (defined($opt{$name})) {
  475. return $opt{$name};
  476. }
  477. return undef;
  478. }
  479. # First we need to do is the builds
  480. for (my $i = 1; $i <= $opt{"NUM_BUILDS"}; $i++) {
  481. my $type = "BUILD_TYPE[$i]";
  482. $noclean = set_build_option("BUILD_NOCLEAN", $i);
  483. $minconfig = set_build_option("MIN_CONFIG", $i);
  484. $run_test = set_build_option("TEST", $i);
  485. doprint "\n\n";
  486. doprint "RUNNING TEST $i of $opt{NUM_BUILDS} with option $opt{$type}\n\n";
  487. if ($opt{$type} eq "bisect") {
  488. bisect $i;
  489. next;
  490. }
  491. if ($opt{$type} ne "nobuild") {
  492. build $opt{$type};
  493. }
  494. get_grub_index;
  495. get_version;
  496. install;
  497. monitor;
  498. if (defined($run_test)) {
  499. do_run_test;
  500. }
  501. success $i;
  502. }
  503. if ($opt{"POWEROFF_ON_SUCCESS"}) {
  504. halt;
  505. } else {
  506. reboot;
  507. }
  508. exit 0;