ktest.pl 12 KB

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