Ruby Code Duplication Detection with Flay

Flay is terribly useful, but has terribly usability …

If the repo was not such a mess I’d make PRs to fix it, but tests are not even runnable and PRs to make the Readme readabe got rejected … so I’m not going to bother … a small excerpt from Samson
More config options can be found on it’s homepage
Just wanted to share this useful rake task which found a bunch of duplication and already prevented me from adding new duplication twice 🙂

desc "Analyze for code duplication (large, identical syntax trees) with fuzzy matching."
task :flay do
  require 'flay' # do not require in production
  files = Dir["{config,lib,app/**/*.{rb,erb}"]
  files -= [
    # Things you want to ignore
  ]
  flay = Flay.run([*files, '--mass', '25']) # mass threshold is shown mass / occurrences
  abort "Code duplication found" if flay.report.any?
end
Advertisements

Ad-hoc Rack Test Servers for Integration Tests

Boots up test servers so integration tests can connect to them (when running in the same process try webmock) … works for RSpec and Minitest (with minitest-around/maxitest or similar gem)

The server boot takes about 5 seconds, might be better with a different web-server, but WEBrick is simplest.

Code from this post is now the stub_server gem.

require 'stub_server'

describe 'CLI' do
  let(:service_a_replies) {{'/v1/catalog/services' => {}}}
  let(:service_b_replies) {{'/api/v1/nodes' => {}}}

  around do |test|
    StubServer.open(8500, service_a_replies) do |a|
      StubServer.open(9000, service_b_replies) do |b|
        a.wait
        b.wait
        test.call
      end
    end
  end

  it "works" do
    ...
  end
end

Stubbing Ruby AWS SDK XML with webmock

Stubbing the XML AWS expects is not easy (expects lists to have member keys) and has lots of repetitive elements like XyzResponse + XyzRequest … so I wanted to share a few useful helpers that make it dry.

(Alternative: use stub_requests, example PR)

  # turn ruby hashes into aws style xml
  def fake_xml(name, body={})
    xml = {"#{name}Result" => body}
      .to_xml(root: "#{name}Response", camelize: true).
      .gsub(/ type="array"/, '')
    loop do
      break unless xml.gsub!(%r{<(\S+)s>\s*<\1>(.*?)</\1>\s*</\1s>}m, "<\\1s><member>\\2</member></\\1s>")
    end
    xml
  end

  def expect_aws_request(method, url, action, response={})
    request = stub_request(method, url).
      with(:body => /Action=#{action}(&|$)/)
    request = if response.is_a?(Exception)
      request.to_raise(response)
    else
      request.to_return(:body => fake_xml(action, response))
    end
    requested << request
    request
  end

  def expect_upload_certificate
    expect_aws_request(
      :post, "https://iam.amazonaws.com/",
      "UploadServerCertificate",
      {server_certificate_metadata: {arn: 'FAKE-ARN'}}
    )
  end

  after { requested.each { |r| assert_requested r } }

  it "uploads a cert" do
    expect_upload_certificate
    manager.upload.must_equal 'FAKE-ARN'
  end