Yes, well, this got published accidentally, a bit too soon. More cryptic than usual. But no one complained, that’s odd. Are you out there?

When Tozier arrives, he raises the question: if a file and/or folder already exists do we want to overwrite it? The answer is that whatever is in our source folder is assumed to be the version we want. We don’t remove anything but we will overwrite even a newer copy with what’s in the Dropbox source folder. (I can imagine changing this story later. This is the story for now.)

This question makes us want to test whether FTP put overwrites, or fails when the file exists. I predict it will overwrite but that’s not how we do this. We test:

  def test_upload_files
    ftp = ftp_connected_to_target
    oldDir = Dir.pwd
    Dir.chdir("/Users/ron/Dropbox/_source")
    source = Dir.glob('**/*').sort
    source.each do |file_name|
      if File::directory? file_name
        ftp_mkdir_safely(ftp, file_name)
      else
        ftp.putbinaryfile(file_name,file_name)
        ftp.putbinaryfile(file_name,file_name)
      end
    end
    ftp.close
    Dir.chdir(oldDir)
    Dir.chdir("_target")
    target = Dir.glob('**/*').sort
    assert_equal(source, target)
    # puts target
    Dir.chdir(oldDir)
  end

We begin by duplicating the putbinaryfile call. This will fail if attempts to overwrite fail. We are assuming – and you know what happens when you assume – that if the write doesn’t fail, it will overwrite.

It doesn’t fail, so our answer looks like overwriting is what we get. Since it’s what we want, we’re nearly happy.

But we’re trying to build Confidence1 here, so we write another test, this time putting new contents over the first one:

  def test_overwrite_works
    ftp = ftp_connected_to_target
    oldDir = Dir.pwd
    Dir.chdir("/Users/ron/Dropbox/_source")
    ftp.putbinaryfile("a.txt", "overwritten.txt")
    ftp.putbinaryfile("b.txt", "overwritten.txt")
    expected = File.read('b.txt')
    ftp.close
    Dir.chdir(oldDir)
    stuff = File.read('_target/overwritten.txt')
    assert_equal(expected, stuff)
  end

Cute, huh? And not really harder to write. Now we’re really sure that FTP:putbinaryfile does overwrite without complaint, and that’s what we want.

Let’s now see about end-to-end.We have a bunch of notes about how it’s supposed to work and we sketch our test:

  # copy _source to working_folder/articles
  # run jekyll (in working_folder)
  # ftp only the copied and jekylled files from working_folder/_site/articles to remote site
  # with <any>.md -> <any>.html

  # check_contents:
    # the YAML worked
    # the jpegs are still viewable
    # categories are done and uploaded

  def test_end_to_end
    site = ftp_pointing_somewhere_safe
    jr = JekyllRunner.new(site)
    jr.run
    read_site_and_check_contents
  end

Naturally this isn’t valid code yet but we certainly have a failing test, so we’re allowed to do work.

We think the next step is to install jekyll in the test folder. I have a major concern: not screwing up the existing jekyll installation. We just do a jekyll new and figure we’ll work out any folder issues. We now have a naked Jekyll installation under test-ftp folder. We run Jekyll build and it works. (I consider this to be a miracle and expect that with one more I’ll be canonized.)

We have our source folder and it has subfolders the way we want them, and files in the folders. This was good enough for testing our code to move from the source into the site, but we’ll need to have some markdown files to check Jekyll. The issue here is that the site source will have .md files but we need to move the corresponding .html files from the Jekyll _site folder. So for a valid test we need some markdown. We decide to put this off, since we’re not running Jekyll yet anyway.

We press on to improve our new class so that it moves the files:

class JekyllRunner
  def initialize(ftp_to_site)
    @ftp = ftp_to_site
  end

  def run
    move_ipad_files
    run_jekyll
    ftp_the_results_to_site
  end
end

This, of course, is what I was taught to call “Programming By Intention”. You just write calls to the things you want to have happen and then implement the things.

After some milling about, we write a test to drive our non-existent implementation:

  def test_file_copy
    FileUtils.rm_rf("./test_jekyll_site/articles")
    ipad_folder = '/Users/ron/Dropbox/_source/.' # dot is key
    jekyll_folder = './test_jekyll_site'
    ftp = nil 
    jr = JekyllRunner.new(ipad_folder, jekyll_folder, ftp)
    jr.move_ipad_files # sorry, Demeter
    assert(File.exist?('./test_jekyll_site/articles/a.txt'), "can't find a.txt")
  end

Strictly speaking, a user of JekyllRunner should never need or use access to the move_ipad_files function: it’s internal. So we apologize to Demeter for not following her Excellent Suggestion. This drives out some implementation, which you can see below. That test worked quickly so we also put in a quick one that tests running Jekyll.

require "minitest/autorun"
require "net/ftp"
require "./passwords"
require "set"

class Test_JekyllRunner  < Minitest::Test

  def setup
    @folder = Dir.pwd
  end

  def teardown
    Dir.chdir(@folder)
  end

  # copy _source to working_folder/articles
  # run jekyll (in working_folder)
  # ftp only the copied and jekylled files from working_folder/_site/articles to remote site
  # with <any>.md -> <any>.html

  # check_contents:
    # the YAML worked
    # the jpegs are still viewable
    # categories are done and uploaded

  def test_file_copy
    FileUtils.rm_rf("./test_jekyll_site/articles")
    ipad_folder = '/Users/ron/Dropbox/_source/.' # dot is key
    jekyll_folder = './test_jekyll_site'
    ftp = nil 
    jr = JekyllRunner.new(ipad_folder, jekyll_folder, ftp)
    jr.move_ipad_files # sorry, Demeter
    assert(File.exist?('./test_jekyll_site/articles/a.txt'), "can't find a.txt")
  end

  def test_jekyll_run
    FileUtils.rm_rf("./test_jekyll_site/articles")
    ipad_folder = '/Users/ron/Dropbox/_source/.' # dot is key
    jekyll_folder = './test_jekyll_site'
    ftp = nil 
    jr = JekyllRunner.new(ipad_folder, jekyll_folder, ftp)
    jr.move_ipad_files # sorry, Demeter
    jr.run_jekyll # oops, Demeter, my bad
    assert(File.exist?('./test_jekyll_site/_site/articles/a.txt'), "can't find jekyllated a.txt")
  end

  def test_setup_teardown_restores_chdir
    result = (`pwd`).chomp
    assert_equal(@folder, result)    
  end
    
  def test_chdir_affects_where_backtick_runs
    Dir.chdir('/Users/ron')
    result = (`pwd`).chomp
    assert_equal('/Users/ron', result)
  end
end

class JekyllRunner
  def initialize(ipad_folder, jekyll_folder, ftp_to_site)
    @ipad = ipad_folder
    @jekyll_folder = jekyll_folder
    @ftp = ftp_to_site
  end

  def move_ipad_files
    FileUtils.cp_r(@ipad,"#{@jekyll_folder}/articles")
  end

  def run_jekyll
    pwd = Dir.pwd # TODO fix this to be more reasonable
    Dir.chdir(@jekyll_folder)
    `jekyll build`
    Dir.chdir(pwd)
  end

  def run
    move_ipad_files
    run_jekyll
    # ftp_the_results_to_site
  end
end

The test for Jekyll running is trivial. We just run it and then look to see if there are any files in the _site folder.

There was an Eror™ even in this trivial test and implementation, of course. We didn’t remember to destroy the _site folder before running the test, which meant that we were not really sure whether the run had worked this time, or just some time in the past.

At this point we called it quits for the day. See you Thursday.

  1. Confidence is a term I’m trying out for use in the New Framework series.