text: #!/usr/bin/env python3
  
  # Do the bad thing and import files from the parent folder. 
  # import sys, os
  # sys.path.insert(0, os.path.abspath(os.pardir))
  
  from gcal_helpers import helpers as h
  #from gcal_helpers import config 
  
  import datetime, pytz, dateutil.tz
  import pytest 
  import os
  import json
  import pprint
  
  
  # ==== CONSTANTS
  
  MD_IN = "data_markdown_in"
  MD_OUT = "data_markdown_out"
  
  JSON_IN = "data_json_in"
  RSS_OUT = "data_rss_out"
  NEWSLETTER_OUT = "data_newsletter_out"
  SIDEBAR_OUT = "data_sidebar_out"
  
  # For counting numbers of elements
  ITEM_COUNT_OUT = "data_itemcount_out"
  
  TMPDIR = "/tmp/pytest-temp"
  
  TESTCONFIG = 'testing_config.py'
  
  
  # ==== TEST DATA
  
  # To add Markdown/Json files, add them to the proper folder with the 
  # right extension. The harness should pick up files automatically.
  
  # http://stackoverflow.com/questions/23988853/how-to-mock-set-system-date-in-pytest
  # Wow this is fragile, though. It assumes that every test case will
  # have the same generation time. 
  # In the RSS, set 
  # <lastBuildDate>Wed, 19 Apr 2017 15:01:56 -0400</lastBuildDate>
  FAKE_NOW = datetime.datetime(
      2017, 4, 19, 
      15, 1, 56, 
      tzinfo=dateutil.tz.tzoffset(None, -14400)
      )
  
  # Format: googledate, rfc822, human, human-dateonly, 
  #   short-human-dateonly, short-human-datetime 
  #   explanation
  # This is because I am testing many different 
  # functions with the same input.
  DATE_EXAMPLES = [
      ("2016-04-07T20:10.000Z", 
          "Thu, 07 Apr 2016 20:10:00 +0000",
          "Thursday, Apr 07 2016,  8:10pm",
          "Thursday, Apr 07 2016",
          "Thu, Apr  7",
          "Thu, Apr  7,  8:10pm",
          "Regular date with UTC",
          ),
      ("2017-04-07T20:10.000EDT", 
          "Fri, 07 Apr 2017 20:10:00 -0400",
          "Friday, Apr 07 2017,  8:10pm",
          "Friday, Apr 07 2017",
          "Fri, Apr  7",
          "Fri, Apr  7,  8:10pm",
          "Regular date with EDT",
          ),
      ("1970-01-01T00:00.000Z", 
          "Thu, 01 Jan 1970 00:00:00 +0000",
          "Thursday, Jan 01 1970, 12:00am",
          "Thursday, Jan 01 1970",
          "Thu, Jan  1",
          "Thu, Jan  1, 12:00am",
          "Start of epoch",
          ),
      ("2016-04-07T23:59.000Z", 
          "Thu, 07 Apr 2016 23:59:00 +0000",
          "Thursday, Apr 07 2016, 11:59pm",
          "Thursday, Apr 07 2016",
          "Thu, Apr  7",
          "Thu, Apr  7, 11:59pm",
          "Final minute",
          ),
      ]
  
  
  
  # ==== Helper Functions 
  
  def set_config():
      """ Set dummy config file.
      """
      global config
      config = h.load_config(
          os.path.join(
              os.path.dirname(os.path.abspath(__file__)),
              TESTCONFIG,
              )
          )
  
  
  
  def save_to_temp(filename, output):
      """ Save output to a tempdir so I can generate 
          outputs.
      """
  
      # Does the temp dir exist?
      # Let's hope it is not just a file.
      if not os.path.isdir(TMPDIR):
          os.makedirs(TMPDIR)
  
      filepath = os.path.join(TMPDIR, filename)
      with open(filepath, "w") as f: 
          f.write(output)
  
  
  
  def pickdate(target, datelist):
      """ Pick the date from a list of tuples.
          target: an index > 1
      """
  
      return list(
          map(
              lambda x : (x[0], x[target]),
              datelist
              )
          )
  
  def tally_organized_list(orglist):
      """ Given a list generated by h.organize_events_by_day, 
          count the occurences of each header. 
          eg [[Monday, 3], [Wednesday, 1], [Thursday, 3]]
          with the days spelled out as human days.
      """
      retval = []
      for day in orglist:
          # I wanted this to output tuples, but it is hard to 
          # read tuples from json.loads, so give up and just 
          # make everything a mutable list of lists.
          retval.append( [day, len(orglist[day]) ] )
  
      return retval
        
  
  # ===== MONKEYPATCHES 
  
  @pytest.fixture
  def patch_datetime_now(monkeypatch):
      set_config()
      class mydatetime:
          @classmethod
          # Gah. I have to account for timezone input.
          def now(cls, tz=pytz.timezone('America/Toronto')):
              return FAKE_NOW
      monkeypatch.setattr(datetime, 'datetime', mydatetime)
  
  # Hrm. Google link shortener can return different short URLs
  # for the same long link, so ignore for my tests.
  @pytest.fixture
  def patch_google_shortener(monkeypatch):
      def my_shorten_url(longurl):
          return longurl
      monkeypatch.setattr(h, 'shorten_url', my_shorten_url)
      
  @pytest.fixture
  def patch_newsletter_limit_infinite(monkeypatch):
      """ Make sure limited newsletter lengths do not mess up 
          the test cases.
      """
      set_config()
      monkeypatch.setattr(config, 'NEWSLETTER_MAX_DAYS', None)
  
  @pytest.fixture
  def patch_newsletter_limit_small(monkeypatch):
      set_config()
      monkeypatch.setattr(config, 'NEWSLETTER_MAX_DAYS', 2)
  
  
  def get_testfile_path(filename, datadir, ext=""):
      """ ext should be something like ".html"
      """
  
      fullfile = "{}{}".format(filename,ext)
      return os.path.join(
          os.path.dirname(__file__),
          datadir,
          fullfile,
          )
  
  # ==== GET FILES 
  
  def get_file_as_string(filename, datadir, ext="", create_file=False):
      filepath = get_testfile_path(filename, datadir, ext)
  
      try:
          with open(filepath, "r") as f:
                  filetext = f.read()
      except FileNotFoundError:
          if create_file:
              with open(filepath, "w") as t:
                  t.write("")
                  filetext = ""
          else:
                  raise
  
      return filetext
      
  
  def get_markdown_files(testname):
      intext = get_file_as_string(testname, MD_IN, ".md")
      outtext = get_file_as_string(testname, MD_OUT, ".html", create_file=True)
  
      return (intext, outtext)
      
  
  def get_rss_files(testname):
      jsontext = get_file_as_string(testname, JSON_IN, ".json")
      jsondict = json.loads(jsontext)
  
      rsstext = get_file_as_string(testname, RSS_OUT, ".rss", create_file=True)
      return (jsondict, rsstext)
  
  
  def get_newsletter_files(testname):
      jsontext = get_file_as_string(testname, JSON_IN, ".json")
      jsondict = json.loads(jsontext)
  
      news_text = get_file_as_string(
          testname, 
          NEWSLETTER_OUT,
          ".txt", 
          create_file=True
          )
      return (jsondict, news_text)
  
  def get_sidebar_files(testname):
      """ This should be merged with get_newsletter_files"""
      jsontext = get_file_as_string(testname, JSON_IN, ".json")
      jsondict = json.loads(jsontext)
  
      sidebar_text = get_file_as_string(
          testname, 
          SIDEBAR_OUT,
          ".html", 
          create_file=True
          )
      return (jsondict, sidebar_text)
  
  
  def get_testfiles(infolder, extension):
      """ Find all files with the given extension in 
          the given folder. Then return the barenames without the 
          extension (eg ".json")
  
          In this way I can drop testfiles into a folder and not worry 
          about updating testing code. 
      """
  
      targetfolder = os.path.join(os.path.dirname(__file__), infolder)
      allfiles = os.listdir(targetfolder)
      targetfiles = []
      chop = -1 * len(extension)
      # I ought to use filter here. I suck.
      for f in allfiles:
          if f.endswith(extension):
              # eg for .json chop is -5, so we are slicing the
              # last five characters from the filename
              targetfiles.append(f[:chop])
  
      return targetfiles
  
      
  
  # ==== TEST DATES 
  
  @pytest.mark.parametrize(
      "googledate, target",
      pickdate(2, DATE_EXAMPLES),
      )
  def test_human_date(googledate, target):
      assert h.get_human_datestring(googledate) \
          == target
  
  @pytest.mark.parametrize(
      "googledate, target",
      pickdate(3, DATE_EXAMPLES),
      )
  def test_human_dateonly(googledate, target):
      assert h.get_human_dateonly(googledate) \
          == target
  
  @pytest.mark.parametrize(
      "googledate, target",
      pickdate(1, DATE_EXAMPLES),
      )
  def test_rfc822(googledate, target):
      assert h.get_rfc822_datestring(googledate) \
          == target
  
  @pytest.mark.parametrize(
      "googledate, target",
      pickdate(4, DATE_EXAMPLES),
      )
  def test_short_human_dateonly(googledate, target):
      assert h.get_short_human_dateonly(googledate) \
          == target
  
  
  @pytest.mark.parametrize(
      "googledate, target",
      pickdate(5, DATE_EXAMPLES),
      )
  def test_short_human_datetime(googledate, target):
      assert h.get_short_human_datetime(googledate) \
          == target
  
  
  
  @pytest.mark.xfail(reason="parsedate chokes on 0000")
  def test_year_zero():
      assert h.get_human_date("0000-12-29T00:00.000Z") \
          == "Friday, December 29 0000"
  
  
  
  # ==== TEST MARKDOWN 
  
  @pytest.mark.parametrize(
      "testcase", 
      get_testfiles(MD_IN, ".md"),
      )
  def test_markdown(testcase):
      (intext, outtext) = get_markdown_files(testcase)
      assert h.get_markdown(intext) == outtext
  
  
  # ==== TEST JSON TO RSS 
  
  @pytest.mark.parametrize(
      "testcase",
      get_testfiles(JSON_IN, ".json"),
      )
  def test_json_to_rss(testcase, patch_datetime_now):
      (injson, outrss) = get_rss_files(testcase)
      testrss = h.generate_rss(injson)
      
      try: 
          assert testrss == outrss
      except AssertionError:
          # Use this to generate output for future runs
          save_to_temp("{}.rss".format(testcase), testrss)    
          raise
  
  # ==== TEST NEWSLETTER DAY RESTRICTIONS
  
  @pytest.mark.parametrize( "testcase",
      ["01-fullcalendar.json",
       "07-twodays-midnight-truncate.json",
       "08-event-day-gap.json",
      ]
      )
  @pytest.mark.parametrize( "num_days", list(range(0, 14, 3)))
  def test_organize_events_by_day(
      testcase,
      num_days,
      patch_datetime_now,
      patch_newsletter_limit_small,
      ):
  
      in_text = get_file_as_string(testcase, JSON_IN,)
      
      in_dict = json.loads(in_text)
  
      in_items = in_dict['items']
      sorted_items = h.organize_events_by_day(in_items, num_days)
  
      # pprint.pprint(sorted_items)
  
      assert len(sorted_items) <= num_days
  
  
  
  @pytest.mark.parametrize( "testcase",
      ["01-fullcalendar",
       "07-twodays-midnight-truncate",
       "08-event-day-gap",
      ]
      )
  @pytest.mark.parametrize( "num_days", [2, 4, None])
  def test_organize_events_exact_items(
      testcase,
      num_days,
      patch_datetime_now,
      ):
  
      in_text = get_file_as_string(testcase, JSON_IN, ext=".json")
      in_dict = json.loads(in_text)
  
      in_items = in_dict['items']
      sorted_items = h.organize_events_by_day(in_items, num_days)
      item_tally = tally_organized_list(sorted_items)
  
      out_filename = "{}--limit-{}".format(testcase, num_days)
      out_json = get_file_as_string(
          out_filename, 
          ITEM_COUNT_OUT,
          ext="",
          create_file=True,
          )
      if out_json != "":
          out_list = json.loads(out_json)
      else: 
          out_list = ""
  
      try:
          assert out_list == item_tally
      except AssertionError:
          item_tally_json = json.dumps(
              item_tally, 
              indent=2, 
              separators=(',', ': '),
              )
          save_to_temp(out_filename, item_tally_json)
          raise
  
  
  # ==== TEST NEWSLETTER GENERATION 
  
  
  @pytest.mark.parametrize(
      "testcase",
      get_testfiles(JSON_IN, ".json"),
      )
  def test_json_to_newsletter(
      testcase, 
      patch_datetime_now,
      patch_google_shortener,
      patch_newsletter_limit_infinite,
      ):
      (injson, outtxt) = get_newsletter_files(testcase)
      test_newsletter = h.generate_newsletter(injson)
      
      try: 
          assert test_newsletter == outtxt
      except AssertionError:
          # Use this to generate output for future runs
          save_to_temp("{}.txt".format(testcase), test_newsletter)    
          raise
  
  # ==== TEST SIDEBAR GENERATION (yuk)
  
  
  @pytest.mark.parametrize(
      "testcase",
      get_testfiles(JSON_IN, ".json"),
      )
  def test_json_to_sidebar(
      testcase, 
      ):
      (injson, outtxt) = get_sidebar_files(testcase)
      test_sidebar = h.generate_sidebar(injson)
      
      try: 
          assert test_sidebar == outtxt
      except AssertionError:
          # Use this to generate output for future runs
          save_to_temp("{}.html".format(testcase), test_sidebar)    
          raise
  