amp / lib / amp / extensions / bugs.rb

require 'stringio'

module Amp
  class Bug
    
    def self.bugs
      @@bugs ||= []
    end
    
    def initialize
      @setup   = proc {} # do nothing
      @success = proc {} # do nothing
      @cleanup = proc {} # do nothing
      
      self.class.bugs << self
      
      if block_given?
        yield self
      end
    end
    
    ##
    # The block (optionally) passed into #setup is what sets up the bug for testing.
    # It should do everything that needs to be done before testing for the existence
    # of the bug.
    # 
    # If a block is passed, store it. Else, run the current saved block
    # (which defaults to `proc {}`) in swizzled IO. That is, run the block
    # and capture the output of $stdout, and then return it.
    # 
    # @yield b The block which sets up the bug
    # @return [Proc, String] if a block is passed in, it will return that.
    #   Else, it will return the $stdout output of running the proc
    def setup(&b) # because when `&b` makes the block optional
      if b # if a block is given, save it
        @setup = b
      else # else, run the block with swizzled io
        capture_stdout { @setup.call }
      end
    end
    
    ##
    # The block (optionally) passed into #success is what determines if the bug
    # exists.
    # 
    # If a block is passed, store it. Else, run the current saved block
    # (which defaults to `proc {}`) in swizzled IO. That is, run the block
    # and capture the output of $stdout, and then return it.
    # 
    # @yield b The block which sets up the bug
    # @return [Proc, Object] if a block is passed in, it will return that.
    #   Else, it will return the result of the proc
    def success(&b) # because when `&b` makes the block optional
      if b # if a block is given, save it
        @success = b
      else # else, run the block
        @success.call
      end
    end
    alias_method :success?, :success
    
    ##
    # The block (optionally) passed into #success is what determines if the bug
    # exists.
    # 
    # If a block is passed, store it. Else, run the current saved block
    # (which defaults to `proc {}`) in swizzled IO. That is, run the block
    # and capture the output of $stdout, and then return it.
    # 
    # @yield b The block which sets up the bug
    # @return [Proc, TrueClass] if a block is passed in, it will return that.
    #   Else, it will return nil
    def cleanup(&b) # because when `&b` makes the block optional
      if b # if a block is given, save it
        @cleanup = b
      else # else, run the block
        @cleanup.call
        nil
      end
    end
    
    ##
    # A short description of the bug.
    # If you pass in a number, it sets
    # this bug's desc to +d+. Else, it tells you what the description is.
    # 
    # @param  [String] d The bug's new descsription.
    # @return [String] The bug's brief description.
    def desc(d=nil)
      d ? @desc = d : @desc
    end
    
    ##
    # What number bug is this on the Lighthouse site?
    # Used for identification purposes. If you pass in a number, it sets
    # this bug's number to +n+. Else, it tells you what the number is.
    # 
    # @param  [Integer] n The ticker number on Lighthouse.
    # @return [Integer] The bug's ticket number.
    def number(n=nil)
      n ? @number = n : @number
    end
    
    ##
    # Mandatory block. This captures what accumulates in STDOUT during the block
    # and returns it. Does not get STDERR.
    # 
    # @return [String] Whatever accumulates in STDOUT during the block.
    def capture_stdout
      old, $stdout = $stdout, StringIO.new
      yield
      old, $stdout = $stdout, old # all is right with the world
      old.string
    end
  end
end

namespace :bugs do
  
  command :test do |c|
    c.desc "Run a test file for a bug number in the bugs/ directory."
    c.opt  :"setup-only",   "Only do the setup portion – no cleanup"
    c.opt  :"cleanup-only", "Only do the cleanup portion – no setup"
    c.opt  :loud,  "Show the output of each bug"
    c.opt  :stfu,  "Don't have any output. At all. Ever."
    
    c.on_run do |o, a|
      a.each {|bug| load "bugs/#{bug}.rb" }
      
      # hide output
      if o[:stfu]
        # just fyi, it'd be more efficient to create a "bottomless pit"
        # class and use something that just throws away everything #puts-ed
        # into it. But I don't think this is worth creating a new class for
        # and cluttering our already massive codebase.
        old, $stdout = $stdout, StringIO.new
      end
      
      Amp::Bug.bugs.each do |bug|
        print "#{bug.number}: " # print the bug number
        
        unless o[:"cleanup-only"] # if we're only doing cleanup, don't do the setup
          output = bug.setup # run the setup and capture the output
          
          if o[:loud]
            output.split("\n").each {|l| puts "\t#{l}" } # print the line
          end

          next if o[:"setup-only"] # if just setup, then exit before cleanup
        end
        
        # Only test for success if we're doing things normally:
        # setup AND cleanup. Don't test for success if we're only doing one
        unless o[:"setup-only"] || o[:"cleanup-only"]
          # determine success and report to the user
          bug.success? ? puts('Success'.green) : puts('Failure'.red)
        end
        
        bug.cleanup # and clean up our mess
        puts "Cleaned" if o[:"cleanup-only"]
      end
      
      # revert back to normal
      if o[:stfu]
        $stdout = old
      end
    end
  end
end

command :bisect_bug do |c|
  c.desc "Easy shortcut for doing `amp bisect` while using the `bugs:test` command."
  
  c.on_run do |o, a|
    raise "Too many bisects going on! Only one argument accepted" if a.size > 1
    o[:command] = "amp bugs:test --quiet #{a.first}"
    Amp::Command['bisect'].run o, a
  end
end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.