Skip to content
Permalink
Browse files

Makes feedvalidator.py extensible and adds feedvalidator_googletransi…

…t.py for using the googletransit extension without having to add the -extension flag.

Added possibility to inherit from the testfeedvalidator FullTests in order to use them for testing the new extension executable. Added new test data folder in test/data/googletransit and added a good_feed to it which uses some extension features.

Executables for other extensions can now be created as in feedvalidator_googletransit.py. These executables can then be used to create Windows executables using py2exe. Therefore they have to be registered in setup.py.

Review at http://codereview.appspot.com/4294044/
  • Loading branch information...
Moritz Neun
Moritz Neun committed Mar 22, 2011
1 parent b1a6468 commit b07c27b483721527c121856366347e757344570d
@@ -614,6 +614,11 @@ def CheckVersion(latest_version=''):


def main():
(feed, options) = ParseCommandLineArguments()
return RunValidationFromOptions(feed, options)


def ParseCommandLineArguments():
usage = \
'''%prog [options] [<input GTFS.zip>]
@@ -683,9 +688,13 @@ def main():
parser.error('You must provide the path of a single feed')
else:
feed = args[0]

feed = feed.strip('"')

return (feed, options)


def RunValidationFromOptions(feed, options):
"""Validate feed, run in profiler if in options, and return an exit code."""
if options.performance:
return ProfileRunValidationOutputFromOptions(feed, options)
else:
@@ -0,0 +1,32 @@
#!/usr/bin/python2.5

# Copyright (C) 2011 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Extension of feedvalidator.py using the googletransit extension by default.
"""

import extensions.googletransit # needed for py2exe
import feedvalidator
from transitfeed import util


def main():
(feed, options) = feedvalidator.ParseCommandLineArguments()
options.extension = 'extensions.googletransit'
return feedvalidator.RunValidationFromOptions(feed, options)


if __name__ == '__main__':
util.RunWithCrashHandler(main)
@@ -0,0 +1,2 @@
agency_id,agency_name,agency_url,agency_timezone,agency_phone
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314
@@ -0,0 +1,3 @@
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
FULLW,1,1,1,1,1,1,1,20070101,20111231
WE,0,0,0,0,0,1,1,20070101,20111231
@@ -0,0 +1,2 @@
service_id,date,exception_type
FULLW,20070604,2
@@ -0,0 +1,3 @@
fare_id,price,currency_type,payment_method,transfers,transfer_duration,agency_id
p,1.25,USD,0,0,,DTA
a,5.25,USD,0,0,,DTA
@@ -0,0 +1,5 @@
fare_id,route_id,origin_id,destination_id,contains_id
p,AB,,,
p,STBA,,,
p,BFC,,,
a,AAMV,,,
@@ -0,0 +1,2 @@
feed_publisher_name,feed_publisher_url,feed_lang,feed_timezone,feed_valid_from,feed_valid_until
Autorité de passage de démonstration,http://google.com,en,America/Los_Angeles,20070101,20111231
@@ -0,0 +1,12 @@
trip_id,start_time,end_time,headway_secs,exact_times
STBA,6:00:00,22:00:00,1800,0
CITY1,6:00:00,7:59:59,1800,0
CITY2,6:00:00,7:59:59,1800,0
CITY1,8:00:00,9:59:59,600,0
CITY2,8:00:00,9:59:59,600,0
CITY1,10:00:00,15:59:59,1800,0
CITY2,10:00:00,15:59:59,1800,0
CITY1,16:00:00,18:59:59,600,0
CITY2,16:00:00,18:59:59,600,0
CITY1,19:00:00,22:00:00,1800,0
CITY2,19:00:00,22:00:00,1800,0
@@ -0,0 +1,6 @@
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
AB,DTA,,Airport ⇒ Bullfrog,,3,,,
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,
@@ -0,0 +1,29 @@
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,
CITY1,6:12:00,6:14:00,NADAV,10,,,,
CITY1,6:19:00,6:21:00,DADAN,15,,,,
CITY1,6:26:00,6:28:00,EMSI,20,,,,
CITY2,6:28:00,6:30:00,EMSI,100,,,,
CITY2,6:35:00,6:37:00,DADAN,200,,,,
CITY2,6:42:00,6:44:00,NADAV,300,,,,
CITY2,6:49:00,6:51:00,NANAA,400,,,,
CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
AB1,8:10:00,8:15:00,BULLFROG,2,,,,
AB2,12:05:00,12:05:00,BULLFROG,1,,,,
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
AAMV1,9:00:00,9:00:00,AMV,2,,,,
AAMV2,10:00:00,10:00:00,AMV,1,,,,
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
AAMV3,14:00:00,14:00:00,AMV,2,,,,
AAMV4,15:00:00,15:00:00,AMV,1,,,,
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
@@ -0,0 +1,11 @@
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,,
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION
BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1,
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,,
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,,
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,,
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,,
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,,
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,,
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,,
@@ -0,0 +1,3 @@
from_stop_id,to_stop_id,transfer_type,min_transfer_time
NADAV,NANAA,3,
EMSI,NANAA,2,1200
@@ -0,0 +1,12 @@
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
AB,FULLW,AB1,to Bullfrog,0,1,
AB,FULLW,AB2,to Airport,1,2,
STBA,FULLW,STBA,Shuttle,,,
CITY,FULLW,CITY1,,0,,
CITY,FULLW,CITY2,,1,,
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
BFC,FULLW,BFC2,to Bullfrog,1,2,
AAMV,WE,AAMV1,to Amargosa Valley,0,,
AAMV,WE,AAMV2,to Airport,1,,
AAMV,WE,AAMV3,to Amargosa Valley,0,,
AAMV,WE,AAMV4,to Airport,1,,
@@ -32,12 +32,13 @@

class FullTests(util.TempDirTestCaseBase):

feedvalidator_executable = 'feedvalidator.py'
extension_message = 'FeedValidator extension used: '
extension_name = 'None'

def testGoodFeed(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__, self.GetPath('test', 'data', 'good_feed')])
self.assertTrue(re.search(r'feed validated successfully', out))
self.assertFalse(re.search(r'ERROR', out))
@@ -50,7 +51,7 @@ def testGoodFeed(self):

def testGoodFeedConsoleOutput(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__,
'--output=CONSOLE', self.GetPath('test', 'data', 'good_feed')])
self.assertTrue(re.search(r'feed validated successfully', out))
@@ -62,7 +63,7 @@ def testGoodFeedConsoleOutput(self):

def testMissingStops(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__,
self.GetPath('test', 'data', 'missing_stops')],
expected_retcode=1)
@@ -77,7 +78,7 @@ def testMissingStops(self):

def testMissingStopsConsoleOutput(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '-o', 'console',
[self.GetPath(self.feedvalidator_executable), '-n', '-o', 'console',
'--latest_version', transitfeed.__version__,
self.GetPath('test', 'data', 'missing_stops')],
expected_retcode=1)
@@ -91,7 +92,7 @@ def testMissingStopsConsoleOutput(self):

def testLimitedErrors(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-l', '2', '-n',
[self.GetPath(self.feedvalidator_executable), '-l', '2', '-n',
'--latest_version', transitfeed.__version__,
self.GetPath('test', 'data', 'missing_stops')],
expected_retcode=1)
@@ -105,7 +106,7 @@ def testLimitedErrors(self):

def testBadDateFormat(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__,
self.GetPath('test', 'data', 'bad_date_format')],
expected_retcode=1)
@@ -121,7 +122,7 @@ def testBadDateFormat(self):

def testBadUtf8(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__, self.GetPath('test', 'data', 'bad_utf8')],
expected_retcode=1)
self.assertTrue(re.search(r'ERROR', out))
@@ -135,22 +136,22 @@ def testBadUtf8(self):

def testFileNotFound(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__, 'file-not-found.zip'],
expected_retcode=1)
self.assertFalse(os.path.exists('transitfeedcrash.txt'))

def testBadOutputPath(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__, '-o', 'path/does/not/exist.html',
self.GetPath('test', 'data', 'good_feed')],
expected_retcode=2)
self.assertFalse(os.path.exists('transitfeedcrash.txt'))

def testCrashHandler(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__, 'IWantMyvalidation-crash.txt'],
expected_retcode=127)
self.assertTrue(re.search(r'Yikes', out))
@@ -161,7 +162,7 @@ def testCrashHandler(self):

def testCheckVersionIsRun(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
'100.100.100', self.GetPath('test', 'data', 'good_feed')])
self.assertTrue(re.search(r'feed validated successfully', out))
self.assertTrue(re.search(r'A new version 100.100.100', out))
@@ -174,7 +175,7 @@ def testCheckVersionIsRun(self):

def testCheckVersionIsRunConsoleOutput(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '-n', '-o', 'console',
[self.GetPath(self.feedvalidator_executable), '-n', '-o', 'console',
'--latest_version=100.100.100',
self.GetPath('test', 'data', 'good_feed')])
self.assertTrue(re.search(r'feed validated successfully', out))
@@ -184,8 +185,9 @@ def testCheckVersionIsRunConsoleOutput(self):

def testUsage(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath('feedvalidator.py'), '--invalid_opt'], expected_retcode=2)
self.assertMatchesRegex(r'[Uu]sage: feedvalidator.py \[options\]', err)
[self.GetPath(self.feedvalidator_executable), '--invalid_opt'],
expected_retcode=2)
self.assertMatchesRegex(r'[Uu]sage: feedvalidator(.*).py \[options\]', err)
self.assertMatchesRegex(r'wiki/FeedValidator', err)
self.assertMatchesRegex(r'--output', err) # output includes all usage info
self.assertFalse(os.path.exists('transitfeedcrash.txt'))
@@ -18,13 +18,39 @@

import extensions.googletransit

import os
import re
import time
import transitfeed
from util import MemoryZipTestCase
from util import RecordingProblemAccumulator
from testfeedvalidator import FullTests
from testtransitfeed import ValidationTestCase


class ExtensionFullTests(FullTests):
"""Inherits FullTests from testfeedvalidator.py to test the extension
executable feedvalidator_googletransit.py. Tests the extension executable with
new good_feed test data which uses extension capabilities."""

feedvalidator_executable = 'feedvalidator_googletransit.py'
extension_name = 'extensions.googletransit'

def testGoogleTransitGoodFeed(self):
(out, err) = self.CheckCallWithPath(
[self.GetPath(self.feedvalidator_executable), '-n', '--latest_version',
transitfeed.__version__, self.GetPath('test', 'data', 'googletransit',
'good_feed')])
self.assertTrue(re.search(r'feed validated successfully', out))
self.assertFalse(re.search(r'ERROR', out))
htmlout = open('validation-results.html').read()
self.assertMatchesRegex(
self.extension_message + self.extension_name, htmlout)
self.assertTrue(re.search(r'feed validated successfully', htmlout))
self.assertFalse(re.search(r'ERROR', htmlout))
self.assertFalse(os.path.exists('transitfeedcrash.txt'))


class FareAttributeAgencyIdTestCase(MemoryZipTestCase):
gtfs_factory = extensions.googletransit.GetGtfsFactory()

0 comments on commit b07c27b

Please sign in to comment.
You can’t perform that action at this time.