Homework 3

Please review the material in Lectures 3 and 4 and the concurrency-related slides at the beginning of lecture 7 to answer the questions below.

  1. There is a statement related to software testing that says Testing can only be used to prove the existence of faults not their absence. Explain what this statement means using the testing terminology that we discussed in Lecture 2 (Slide 34). (5 points)
  2. What are the elements of a test case when performing black box testing and how do you know if a blackbox test has passed? Write a simple function in any programming language and then write two blackbox test cases for it. (10 points)
  3. Using the control flow graph from Lecture 2, explain why a test set that achieves statement coverage is not necessarily sufficient to achieve edge coverage. You do not need to prove this assertion in general; keep your remarks to this specific control flow graph first shown on slide 53. (5 points)
  4. What is the difference between how traditional software life cycles measure progress and how agile life cycles measure progress? (5 points)
  5. Consider the following Java program that is split across 6 files. Note: You can download an archive containing this code from the D2L website.


    Product.java:

    public class Product {
    
      private static int _id = 0;
      private String name;
      private int id;
    
      public Product() {
        this.id   = _id;
        this.name = "Product<" + id + ">";
        _id++;
      }
    
      public String toString() {
        return name;
      }
    
      public int id() {
        return id;
      }
    
    }


    ProductionLine.java:

    import java.util.LinkedList;
    import java.util.List;
    
    public class ProductionLine {
    
       private List<Product> products;
    
       public ProductionLine() {
         products = new LinkedList<Product>();
       }
    
       public int size() {
         return products.size();
       }
    
       public void append(Product p) {
         products.add(p);
       }
    
       public Product retrieve() {
         return products.remove(0);
       }
    
    }


    Producer.java:

    public class Producer implements Runnable {
    
      private ProductionLine queue;
    
      public Producer(ProductionLine queue) {
        this.queue = queue;
      }
    
      public void run() {
        int count = 0;
        while (count < 20) {
          if (queue.size() < 10) {
            Product p = new Product();
            System.out.println("Produced: " + p);
            queue.append(p);
            count++;
          }
        }
      }
    
    }


    Consumer.java:

    import java.util.concurrent.ConcurrentHashMap;
    
    public class Consumer implements Runnable {
    
      public static ConcurrentHashMap products =
        new ConcurrentHashMap();
    
      private ProductionLine queue;
    
      public Consumer(ProductionLine queue) {
        this.queue = queue;
      }
    
      public void run() {
        while (true) {
          if (queue.size() > 0) {
            System.out.println("Consumed: " + queue.retrieve());
          } else {
            return;
          }
        }
      }
    
    }


    Monitor.java:

    public class Monitor implements Runnable {
    
      private ProductionLine queue;
    
      public Monitor(ProductionLine queue) {
        this.queue = queue;
      }
    
      public void run() {
        while (true) {
          int size = queue.size();
          if (size > 10) {
            System.out.println("Too many items in the queue: " + size + "!");
          } else if (size == 0) {
            System.out.println("Queue empty!");
          }
          try {
            Thread.sleep(500);
          } catch (Exception ex) {
          }
        }
      }
    
    }


    Main.java:

    import java.util.Arrays;
    
    public class Main {
    
      public static void main(String[] args) {
        ProductionLine queue = new ProductionLine();
    
        Thread monitor = new Thread(new Monitor(queue));
    
        monitor.setDaemon(true);
        monitor.start();
    
        Thread[] consumers = new Thread[5];
    
        Thread[] producers = new Thread[10];
    
        for (int i = 0; i < producers.length; i++) {
          producers[i] = new Thread(new Producer(queue));
          producers[i].start();
        }
    
        for (int i = 0; i < consumers.length; i++) {
          consumers[i] = new Thread(new Consumer(queue));
          consumers[i].start();
        }
    
        for (int i = 0; i < consumers.length; i++) {
          try {
            consumers[i].join();
          } catch (Exception ex) {
          }
        }
    
        Integer [] keys = Consumer.products.keySet().toArray(new Integer[0]);
    
        Arrays.sort(keys);
    
        for (Integer key : keys) {
          System.out.println("" + key);
        }
    
      }
    
    }


    This program should cause 200 products to be created and consumed but it gets nowhere close to that. In addition, it prints out the ids of all the products that were produced in sorted order so that it is easier to understand what products got produced and where the gaps occurred. However, this program behaves very differently each time it is run. Sometimes the monitor is printing out messages saying the queue is empty and sometimes saying the queue is too big (and sometimes not printing anything at all). Clearly something is wrong.

    Please describe what is wrong with this program from the standpoint of its use of concurrency and what problem or problems discused in Lecture 4 are at the root of the observed behavior. (10 points)

    Imagine we tried to fix the program by replacing ProductionLine with this implementation.


    ProductionLine.java:

    import java.util.LinkedList;
    import java.util.List;
    
    public class ProductionLine {
    
       private List<Product> products;
    
       public ProductionLine() {
         products = new LinkedList<Product>();
       }
    
       public synchronized int size() {
         return products.size();
       }
    
       public synchronized void append(Product p) {
         products.add(p);
       }
    
       public synchronized Product retrieve() {
         return products.remove(0);
       }
    
    }

    We find that the program still does not work but its behavior is typically more consistent in that it prints out the ids and terminates each time but there are still gaps in the product that was produced. Explain how this new version of the program tried to fix the initial problem and then describe why the program still fails to operate as intended. (15 points)

  6. For 10 extra credit points, submit a version of the program that runs as intended. Include a README file that describes how you fixed the problem.

This assignment is worth 50 points.

If you do not attempt to fix the program, then please upload a PDF file containing your answers to the D2L website. If you do attempt to fix the program, then please upload a .zip or .tar.gz archive of your submission which will include a PDF file containing the answers to your questions along with the source code of the fixed consumer/producer program to the D2L website before the start of Lecture 9 on Tuesday, September 23rd.

If you work as a team on this assignment, please upload only a single submission to D2L and indicate your team members at the top of your PDF file.


© University of Colorado, Boulder 2014