don't rely on the pty having a big enough buffer for all output
diff --git a/aos/build/build.py b/aos/build/build.py
index 9d2325d..e49f958 100755
--- a/aos/build/build.py
+++ b/aos/build/build.py
@@ -33,7 +33,35 @@
     process: The currently executing test process or None. Synchronized by
         process_lock.
     stopped: True if we're stopped.
+    output: A queue of lines of output from the test.
   """
+
+  class OutputCopier(threading.Thread):
+    """Copies the output of a test from its output pty into a queue.
+
+    This is necessary because otherwise everything locks up if the test writes
+    too much output and fills up the pty's buffer.
+    """
+
+    def __init__(self, name, fd, queue):
+      super(TestThread.OutputCopier, self).__init__(
+          name=(name + '.OutputCopier'))
+
+      self.fd = fd
+      self.queue = queue
+
+    def run(self):
+      with os.fdopen(self.fd) as to_read:
+        try:
+          for line in to_read:
+            self.queue.put(line)
+        except IOError as e:
+# An EIO from the master side of the pty means we hit the end.
+          if e.errno == errno.EIO:
+            return
+          else:
+            raise e
+
   def __init__(self, executable, env, done_queue, start_semaphore):
     super(TestThread, self).__init__(
         name=os.path.split(executable)[1])
@@ -43,18 +71,23 @@
     self.done_queue = done_queue
     self.start_semaphore = start_semaphore
 
+    self.output = queue.Queue()
+
     self.process_lock = threading.Lock()
     self.process = None
     self.stopped = False
     self.returncode = None
-    self.output = None
+    self.output_copier = None
 
   def run(self):
     with self.start_semaphore:
       if self.stopped:
         return
       test_output('Starting test %s...' % self.name)
-      self.output, subprocess_output = pty.openpty()
+      output_to_read, subprocess_output = pty.openpty()
+      self.output_copier = TestThread.OutputCopier(self.name, output_to_read,
+                                                   self.output)
+      self.output_copier.start()
       try:
         with self.process_lock:
           self.process = subprocess.Popen(self.executable,
@@ -69,6 +102,7 @@
         self.returncode = self.process.returncode
         self.process = None
         if not self.stopped:
+          self.output_copier.join()
           self.done_queue.put(self)
 
   def terminate_process(self):
@@ -939,33 +973,30 @@
           running.remove(done)
           with test_output_lock:
             test_output('Output from test %s:' % done.name)
-            with os.fdopen(done.output) as output_file:
-              try:
-                for line in output_file:
-                  if not sys.stdout.isatty():
-                    # Remove color escape codes.
-                    line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
-                  sys.stdout.write(line)
-              except IOError as e:
-# We want to just ignore EIOs from reading the master pty because that just
-# means we hit the end.
-                if e.errno != errno.EIO:
-                  raise e
-              if not done.returncode:
-                test_output('Test %s succeeded' % done.name)
+            try:
+              while True:
+                line = done.output.get(False)
+                if not sys.stdout.isatty():
+                  # Remove color escape codes.
+                  line = re.sub(r'\x1B\[[0-9;]*[a-zA-Z]', '', line)
+                sys.stdout.write(line)
+            except queue.Empty:
+              pass
+            if not done.returncode:
+              test_output('Test %s succeeded' % done.name)
+            else:
+              if done.returncode < 0:
+                sig = -done.returncode
+                test_output('Test %s was killed by signal %d (%s)' % \
+                            (done.name, sig, strsignal(sig)))
+              elif done.returncode != 1:
+                test_output('Test %s exited with %d' % \
+                            (done.name, done.returncode))
               else:
-                if done.returncode < 0:
-                  sig = -done.returncode
-                  test_output('Test %s was killed by signal %d (%s)' % \
-                              (done.name, sig, strsignal(sig)))
-                elif done.returncode != 1:
-                  test_output('Test %s exited with %d' % \
-                              (done.name, done.returncode))
-                else:
-                  test_output('Test %s failed' % done.name)
-                user_output('Aborting because of test failure for %s.' % \
-                            platform)
-                exit(1)
+                test_output('Test %s failed' % done.name)
+              user_output('Aborting because of test failure for %s.' % \
+                          platform)
+              exit(1)
       finally:
         if running:
           test_output('Killing other tests...')