Пример #1
0
  def move_file_and_test(self, src_f, targ_f):
    """Helper function that performs a dupe file move and tests it."""
    # Initial state
    self.cr_load("dupes-initial")
    sm = SettingsManager(self.loc_in_cr('kpawebgen.json'))
    db_path = os.path.join(sm.abspath('kpa_dir'), shadow)
    index_p = self.loc_in_cr("KPA/index.xml")
    kpa_p = self.loc_in_cr("KPA")
    self.cr_nexist(db_path)

    # Create shadow DB
    u = Updater(sm.abspath('kpa_dir'), shadow)
    did = u.update()
    self.assertTrue(did)

    conn = sqlite3.connect(db_path)
    def getall(q, args=()):
      return conn.execute(q, args).fetchall()
    def count_images():
      return getall("""SELECT COUNT(*) FROM image_id""")[0][0]

    (srcID, srcMD5) = getall(
      """SELECT imageID, md5 FROM image_id WHERE path = ?""",
      (src_f,))[0]
    oldCount = count_images()

    # Move a file (in filesystem and XML)
    with open(index_p, 'r') as f:
      xml = f.read()
    xml = xml.replace(src_f, targ_f)
    with open(index_p, 'w') as f:
      f.write(xml)
    os.rename(os.path.join(kpa_p, src_f),
              os.path.join(kpa_p, targ_f))

    # Update shadow DB
    u = Updater(sm.abspath('kpa_dir'), shadow)
    did = u.update()
    self.assertTrue(did)

    # Verify move in shadow DB
    self.assertEqual(getall("""
      SELECT action, oldPath, newPath
      FROM changelog
      WHERE changelog.logID = (SELECT MAX(logID) FROM changelog);"""),
      [('move', src_f, targ_f)])
    (targID, targMD5) = getall(
        """SELECT imageID, md5 FROM image_id WHERE path = ?""",
        (targ_f,))[0]
    self.assertEqual(targID, srcID)
    self.assertEqual(targMD5, srcMD5)
    self.assertEqual(count_images(), oldCount)

    conn.close()
Пример #2
0
class TestSettingsManager(TestBase):
  
  #== Util ==#
  
  s = None
  """SettingsManager for main testing config file."""
  
  def setUp(self):
    self.s = SettingsManager('unit/settings/various.json', self.get_dir())
  
  #== Tests ==#
  
  def test_missing(self):
    """Missing config files must throw an error."""
    self.expect_error(lambda:SettingsManager('does_not_exist.json'), 'Invalid path to settings file')
  
  def test_override(self):
    """User properties must override defaults."""
    self.assertEqual(self.s.require('gal_db_path'), 'foo.bar')
  
  def test_require(self):
    """Retrieval of required non-empty properties."""
    self.expect_error(lambda: self.s.require('12345'), 'missing key',
                      'Require throws error for missing key')
    self.assertEqual(self.s.require('data_dir'), 'data')
  
  def test_catlist(self):
    """Retrieval of lists."""
    self.assertEqual(self.s.require('cats_unconditional', list), ['Foo','*Bar  '])
  
  def test_default_string(self):
    """String is expected by default."""
    self.expect_error(lambda:self.s.require('cats_unconditional'),
                      "Config file needed <type")
  
  def test_abspath(self):
    """Computation of absolute path from absolute and relative paths."""
    self.assertEqual(self.s.abspath('test/abs'), '/tmp')
    
    actual = self.s.abspath('test/rel')
    expected = os.path.join(self.get_dir(), 'unit/settings/beyond')
    self.assertEqual(os.path.realpath(actual), os.path.realpath(expected))
Пример #3
0
  def test_read_standard(self):
    """Read a KPA database and check that the result makes sense."""
    self.cr_load("standard")
    sm = SettingsManager(self.loc_in_cr('kpawebgen.json'))
    r = Reader(sm.abspath('kpa_dir'))
    m = r.read_latest_data()

    self.assertEqual(7, len(m.images))
    beetle = (i for i in m.images if i['path'] == 'IMG_5179.JPG').next()
    beetle_tags = beetle['tags']
    del beetle['tags']
    beetle_timestamp = datetime.datetime(2009, 6, 25, 7, 45, 50)
    expected_beetle = {'imageID':None,
                       'md5':'34f7adfce54bd34bf5f0266dd2c7b800',
                       'path':'IMG_5179.JPG',
                       'width':800,
                       'height':600,
                       'angle':0,
                       'label':'IMG_5179',
                       'description':'Regular public.',
                       'stackOrder':None,
                       'stackID':None,
                       'startDate':beetle_timestamp,
                       'endDate':beetle_timestamp}
    self.assertEqual(beetle, expected_beetle)

    # next() throws if no match
    locat = (i for i in m.cats if i['catName'] == 'Location').next()
    inn_tag = (i for i in m.tags if i['tagName'] == 'Orange Hill Beach Inn' and
                                    i['catID'] == locat['catID']).next()
    island_tag = (i for i in m.tags
                          if i['catID'] == locat['catID'] and
                             i['tagName'] == 'San Salvador Island').next()
    
    self.assertEqual(11, len(beetle_tags)) # no inherit
    beetle_tags.index({'tagID':inn_tag['tagID'],
                       'catID':locat['catID']})

    self.assertEqual(3, len(m.tag_tags))
    m.tag_tags.index({'catID':locat['catID'],
                      'setID':island_tag['tagID'],
                      'tagID':inn_tag['tagID']})
Пример #4
0
class KPAWebGen(object):
  """Core code for gallery generator."""
  
  #== Setup ==#

  # A sequence is an array of 1 or more actions.
  # Actions can be specified by names.
  
  def __init__(self):
    # source of lookup table
    actions = [('read', self.act_read),
               ('localweb', self.act_localweb),
               ('publish', self.act_publish)]
    # save off the ordered names and the lookup table 
    self.action_names = zip(*actions)[0]
    self.action_lookup = dict(actions)
    # TODO: Python subproject shouldn't know about global directory structure
    root = '../../..'
    self.clj_path = ResourceManager.locate(root + '/clj')

  action_names = None
  """Names of program actions, in order."""
  action_lookup = None
  """Dict of action names to functions."""
  sequence_all = 'all'
  """Specifies that all actions should be taken (in order.)"""

  clj_path = None
  """Location of Clojure jar."""
  settings = None
  """SettingsManager object."""
  
  # String -> List<String>
  def get_sequence(self, seq_name):
    """Expand requested sequence into action names. If invalid sequence name,
    throw error with message about allowed names."""
    if seq_name == self.sequence_all:
      return self.action_names
    if seq_name not in self.action_names:
      ls = ", ".join(list(self.action_names)+[self.sequence_all])
      raise Exception("Sequence must be one of: %s" % (ls,))
    return [seq_name]
  
  # String List<String> ->
  def dispatch(self, config_loc, sequence):
    """Dispatch on action names."""
    self.settings = SettingsManager(config_loc)
    for name in sequence:
      print("Running action: %s" % name, file=sys.stdout)
      self.action_lookup[name]()

  def invoke_clojure_action(self, task):
    # We have to use lein instead of launching an uberjar since some of
    # the integration tests are in Clojure, and therefore need to call
    # the .py script...
    # Once everything is in Clojure, this won't be an issue.
    subprocess.check_call(['lein', 'trampoline', 'run',
                           '--task', task,
                           '--config', self.settings.config['_config_path'],
                           '--debug', str(Global.debug).lower()],
                           shell=False, cwd=self.clj_path)
  
  #== Actions ==#
  
  def act_read(self):
    """Update our DB with the latest KPA data."""
    did = Updater(self.settings.abspath('kpa_dir'),
                  self.settings.require('shadow_path')).update(self.force_read)
    if not did:
      print("Skipping read since DB has not changed.", file=sys.stdout)
  
  def act_localweb(self):
    """Generate local website."""
    self.invoke_clojure_action('localweb')
  
  def act_publish(self):
    """Publish to remote website."""
    self.invoke_clojure_action('publish-s3')
  
  #== Interfaces ==#

  force_read = False

  def main(self, args):
    """Validate arguments (minus script name) and call core code."""
    logging.debug('Main args: %s' % args)
    
    if len(args) < 2:
      if len(args) != 1 or args[0] != '--help':
        print("Wrong number of arguments.", file=sys.stderr)
      self.die_usage()
    config_loc = args[0]
    sequence_name = args[1]
    if len(args) == 3 and args[2] == '--force-read':
      self.force_read = True
    
    try:
      sequence = self.get_sequence(sequence_name)
    except Exception as e:
      KPAWebGen.die_top("Could not determine requested action: " + str(e),
                        Constants.ERR_ACTION_INVALID)
    
    try:
      self.dispatch(config_loc, sequence)
    except Exception as e:
      msg = "Error(%s): %s" % (e.__class__.__name__, e)
      KPAWebGen.die_top(msg, Constants.ERR_UNKNOWN_REASON)
  
  @staticmethod
  def die_usage():
    usage = """Usage:
kpawebgen --help
kpawebgen config-file sequence [options]

Options:
--force-read
  Update the shadow DB even if it dopesn't appear to need it."""
    KPAWebGen.die_top(usage, Constants.ERR_BAD_ARGS, isExc=False)
  
  @staticmethod
  def die_top(msgs=None, code=Constants.ERR_NORMAL_EXIT, isExc=True):
    """Die at the top level. Do not call from core code."""
    if Global.debug and isExc:
      traceback.print_exc(file=sys.stdout)
    if isinstance(msgs, basestring):
      msgs = [msgs]
    if msgs:
      for m in msgs:
        print(m, file=sys.stderr)
    sys.exit(code)
Пример #5
0
  def test_firstrun(self):
    """Must create a DB3 if none exists."""
    self.cr_load("firstrun")
    sm = SettingsManager(self.loc_in_cr('kpawebgen.json'))
    db_path = os.path.join(sm.abspath('kpa_dir'), shadow)
    u = Updater(sm.abspath('kpa_dir'), shadow)
    self.cr_nexist(db_path)
    did = u.update()
    self.assertTrue(did)

    self.cr_exist(db_path)
    conn = sqlite3.connect(db_path)

    def getall(q):
      return conn.execute(q).fetchall()
    
    self.assertNotEqual(getall('SELECT xmlChecksum FROM metadata')[0][0], None)
    self.assertEqual(getall('SELECT nextImageID FROM metadata'), [(7,)])
    self.assertEqual(getall('SELECT count(*) FROM image_id'), [(7,)])
    self.assertEqual(getall('SELECT min(imageID) FROM image_id'), [(0,)])
    self.assertEqual(getall('SELECT max(imageID) FROM image_id'), [(6,)])
    self.assertEqual(getall('SELECT count(*) FROM image_int'), [(7,)])
    self.assertEqual(getall('SELECT count(*) FROM image_ext'), [(7,)])
    self.assertEqual(getall('SELECT count(*) FROM changelog'), [(7,)])
    self.assertEqual(getall('SELECT DISTINCT oldPath, oldMD5 FROM changelog'),
                      [(None,None)])
    hornetID = getall("""SELECT imageID FROM image_id
                         WHERE path='IMG_9610.JPG'""")[0][0]
    hornetInt = conn.execute("SELECT * FROM image_int WHERE imageID=?",
                             (hornetID,)).fetchall()[0]
    conn.close()

    # Create the baseline for several changes
    self.dump_db(db_path, 'baseline.dump')
    db_backup = self.loc_in_cr('shadow.db3.firstrun')
    subprocess.call(['cp', db_path, db_backup])

    # No changes yet
    u = Updater(sm.abspath('kpa_dir'), shadow)
    did = u.update()
    self.assertFalse(did) # Don't update if nothing has changed

    # Alter index: Delete and move
    self.patch("KPA/index.xml", "patches/delete+move.index.xml.patch")
    u = Updater(sm.abspath('kpa_dir'), shadow)
    did = u.update()
    self.assertTrue(did)
    self.dump_db(db_path, 'delete+move.dump')
    self.validate_diff("baseline.dump", "patches/delete+move.dump.patch",
                       "delete+move.dump")
    self.patch("KPA/index.xml", "patches/delete+move.index.xml.patch",
               undo=True)
    subprocess.call(['rm', db_path])
    subprocess.call(['cp', db_backup, db_path])

    # Alter index: Create and edit
    self.patch("KPA/index.xml", "patches/create+edit.index.xml.patch")
    u = Updater(sm.abspath('kpa_dir'), shadow)
    did = u.update()
    self.assertTrue(did)
    self.dump_db(db_path, 'create+edit.dump')
    self.validate_diff("baseline.dump", "patches/create+edit.dump.patch",
                       "create+edit.dump")
    self.patch("KPA/index.xml", "patches/create+edit.index.xml.patch",
               undo=True)
    subprocess.call(['rm', db_path])
    subprocess.call(['cp', db_backup, db_path])