2 from nose.tools import *
 
   8 from haversine import haversine
 
   9 from shapely.wkt import loads as wkt_load
 
  10 from shapely.ops import linemerge
 
  12 logger = logging.getLogger(__name__)
 
  14 class NominatimConfig:
 
  18         loglevel = getattr(logging, os.environ.get('LOGLEVEL','info').upper())
 
  19         if 'LOGFILE' in os.environ:
 
  20             logging.basicConfig(filename=os.environ.get('LOGFILE','run.log'),
 
  23             logging.basicConfig(level=loglevel)
 
  24         # Nominatim test setup
 
  25         self.base_url = os.environ.get('NOMINATIM_SERVER', 'http://localhost/nominatim')
 
  26         self.source_dir = os.path.abspath(os.environ.get('NOMINATIM_DIR', '../build'))
 
  27         self.template_db = os.environ.get('TEMPLATE_DB', 'test_template_nominatim')
 
  28         self.test_db = os.environ.get('TEST_DB', 'test_nominatim')
 
  29         self.local_settings_file = os.environ.get('NOMINATIM_SETTINGS', '/tmp/nominatim_settings.php')
 
  30         self.reuse_template = 'NOMINATIM_REMOVE_TEMPLATE' not in os.environ
 
  31         self.keep_scenario_db = 'NOMINATIM_KEEP_SCENARIO_DB' in os.environ
 
  32         os.environ['NOMINATIM_SETTINGS'] = '/tmp/nominatim_settings.php'
 
  34         scriptpath = os.path.dirname(os.path.abspath(__file__))
 
  35         self.scene_path = os.environ.get('SCENE_PATH', 
 
  36                 os.path.join(scriptpath, '..', 'scenes', 'data'))
 
  40         return 'Server URL: %s\nSource dir: %s\n' % (self.base_url, self.source_dir)
 
  42 world.config = NominatimConfig()
 
  45 def write_nominatim_config(dbname):
 
  46     f = open(world.config.local_settings_file, 'w')
 
  47     f.write("<?php\n  @define('CONST_Database_DSN', 'pgsql://@/%s');\n" % dbname)
 
  52 def run_nominatim_script(script, *args):
 
  53     cmd = [os.path.join(world.config.source_dir, 'utils', '%s.php' % script)]
 
  54     cmd.extend(['--%s' % x for x in args])
 
  55     proc = subprocess.Popen(cmd, cwd=world.config.source_dir,
 
  56                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
  57     (outp, outerr) = proc.communicate()
 
  58     logger.debug("run_nominatim_script: %s\n%s\n%s" % (cmd, outp, outerr))
 
  59     assert (proc.returncode == 0), "Script '%s' failed:\n%s\n%s\n" % (script, outp, outerr)
 
  63     return eval('{' + inp + '}')
 
  67     """ Splits a unique identifier for places into its components.
 
  68         As place_ids cannot be used for testing, we use a unique
 
  69         identifier instead that is of the form <osmtype><osmid>[:class].
 
  73         return None, None, None
 
  75     assert_in(osmtype, ('R','N','W'))
 
  77         osmid, cls = oid[1:].split(':')
 
  78         return (osmtype, int(osmid), cls)
 
  80         return (osmtype, int(oid[1:]), None)
 
  84     """ Tries to retrive the place_id for a unique identifier. """
 
  88     osmtype, osmid, cls = world.split_id(oid)
 
  91     cur = world.conn.cursor()
 
  93         q = 'SELECT place_id FROM placex where osm_type = %s and osm_id = %s'
 
  94         params = (osmtype, osmid)
 
  96         q = 'SELECT place_id FROM placex where osm_type = %s and osm_id = %s and class = %s'
 
  97         params = (osmtype, osmid, cls)
 
  98     cur.execute(q, params)
 
  99     assert_equals(cur.rowcount, 1, "%d rows found for place %s" % (cur.rowcount, oid))
 
 100     return cur.fetchone()[0]
 
 104 def match_geometry(coord, matchstring):
 
 105     m = re.match(r'([-0-9.]+),\s*([-0-9.]+)\s*(?:\+-([0-9.]+)([a-z]+)?)?', matchstring)
 
 106     assert_is_not_none(m, "Invalid match string")
 
 108     logger.debug("Distmatch: %s/%s %s %s" % (m.group(1), m.group(2), m.group(3), m.group(4) ))
 
 109     dist = haversine(coord, (float(m.group(1)), float(m.group(2))))
 
 111     if m.group(3) is not None:
 
 112         expdist = float(m.group(3))
 
 113         if m.group(4) is not None:
 
 114             if m.group(4) == 'm':
 
 115                 expdist = expdist/1000
 
 116             elif m.group(4) == 'km':
 
 119                 raise Exception("Unknown unit '%s' in geometry match" % (m.group(4), ))
 
 123     logger.debug("Distances expected: %f, got: %f" % (expdist, dist))
 
 124     assert dist <= expdist, "Geometry too far away, expected: %f, got: %f" % (expdist, dist)
 
 127 def print_statement(element):
 
 128     print '\n\n\n'+str(element)+'\n\n\n'
 
 132 def db_dump_table(table):
 
 133     cur = world.conn.cursor()
 
 134     cur.execute('SELECT * FROM %s' % table)
 
 135     print '\n\n\n<<<<<<< BEGIN OF TABLE DUMP %s' % table
 
 138     print '<<<<<<< END OF TABLE DUMP %s\n\n\n' % table
 
 141 def db_drop_database(name):
 
 142     conn = psycopg2.connect(database='postgres')
 
 143     conn.set_isolation_level(0)
 
 145     cur.execute('DROP DATABASE IF EXISTS %s' % (name, ))
 
 149 world.is_template_set_up = False
 
 152 def db_template_setup():
 
 153     """ Set up a template database, containing all tables
 
 154         but not yet any functions.
 
 156     if world.is_template_set_up:
 
 159     world.is_template_set_up = True
 
 160     world.write_nominatim_config(world.config.template_db)
 
 161     if world.config.reuse_template:
 
 162         # check that the template is there
 
 163         conn = psycopg2.connect(database='postgres')
 
 165         cur.execute('select count(*) from pg_database where datname = %s', 
 
 166                      (world.config.template_db,))
 
 167         if cur.fetchone()[0] == 1:
 
 170         # just in case... make sure a previous table has been dropped
 
 171         world.db_drop_database(world.config.template_db)
 
 172     # call the first part of database setup
 
 173     world.run_nominatim_script('setup', 'create-db', 'setup-db')
 
 174     # remove external data to speed up indexing for tests
 
 175     conn = psycopg2.connect(database=world.config.template_db)
 
 176     psycopg2.extras.register_hstore(conn, globally=False, unicode=True)
 
 178     for table in ('gb_postcode', 'us_postcode'):
 
 179         cur.execute("select * from pg_tables where tablename = '%s'" % (table, ))
 
 181             cur.execute('TRUNCATE TABLE %s' % (table,))
 
 184     # execute osm2pgsql on an empty file to get the right tables
 
 185     osm2pgsql = os.path.join(world.config.source_dir, 'osm2pgsql', 'osm2pgsql')
 
 186     proc = subprocess.Popen([osm2pgsql, '-lsc', '-r', 'xml', '-O', 'gazetteer', '-d', world.config.template_db, '-'],
 
 187                             cwd=world.config.source_dir, stdin=subprocess.PIPE,
 
 188                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 189     [outstr, errstr] = proc.communicate(input='<osm version="0.6"></osm>')
 
 190     logger.debug("running osm2pgsql for template: %s\n%s\n%s" % (osm2pgsql, outstr, errstr))
 
 191     world.run_nominatim_script('setup', 'create-functions', 'create-tables', 'create-partition-tables', 'create-partition-functions', 'load-data', 'create-search-indices')
 
 194 # Leave the table around so it can be reused again after a non-reuse test round.
 
 196 def db_template_teardown(total):
 
 197     """ Set up a template database, containing all tables
 
 198         but not yet any functions.
 
 200     if world.is_template_set_up:
 
 202         if not world.config.reuse_template:
 
 203             world.db_drop_database(world.config.template_db)
 
 205             os.remove(world.config.local_settings_file)
 
 207             pass # ignore missing file
 
 210 ##########################################################################
 
 212 # Data scene handling
 
 216 world.current_scene = None
 
 219 def load_scene(name):
 
 220     if name in world.scenes:
 
 221         world.current_scene = world.scenes[name]
 
 223         with open(os.path.join(world.config.scene_path, "%s.wkt" % name), 'r') as fd:
 
 227                     obj, wkt = line.split('|', 2)
 
 229                     scene[obj.strip()] = wkt_load(wkt)
 
 230             world.scenes[name] = scene
 
 231             world.current_scene = scene
 
 234 def get_scene_geometry(name):
 
 236         # Not a scene description
 
 240     for obj in name.split('+'):
 
 242         if oname.startswith(':'):
 
 243             geoms.append(world.current_scene[oname[1:]])
 
 245             scene, obj = oname.split(':', 2)
 
 246             oldscene = world.current_scene
 
 247             world.load_scene(scene)
 
 248             wkt = world.current_scene[obj]
 
 249             world.current_scene = oldscene
 
 255         return linemerge(geoms)