From 65b8e7251134ef5515c17abae8964c890de0602c Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Thu, 23 Jan 2014 15:38:45 -0500 Subject: [PATCH] Add a new module, TSN.XML.AutoRacingSchedule, and its tests. --- src/Main.hs | 7 + src/TSN/XML/AutoRacingSchedule.hs | 472 ++++++++++++++++++++++++++ test/TestSuite.hs | 4 +- test/shell/import-duplicates.test | 6 +- test/xml/Auto_Racing_Schedule_XML.dtd | 26 ++ test/xml/Auto_Racing_Schedule_XML.xml | 1 + 6 files changed, 512 insertions(+), 4 deletions(-) create mode 100644 src/TSN/XML/AutoRacingSchedule.hs create mode 100644 test/xml/Auto_Racing_Schedule_XML.dtd create mode 100644 test/xml/Auto_Racing_Schedule_XML.xml diff --git a/src/Main.hs b/src/Main.hs index da27b12..8459397 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -45,6 +45,8 @@ import Network.Services.TSN.Report ( report_info, report_error ) import TSN.DbImport ( DbImport(..), ImportResult(..) ) +import qualified TSN.XML.AutoRacingSchedule as AutoRacingSchedule ( + pickle_message ) import qualified TSN.XML.Heartbeat as Heartbeat ( verify ) import qualified TSN.XML.Injuries as Injuries ( pickle_message ) import qualified TSN.XML.InjuriesDetail as InjuriesDetail ( pickle_message ) @@ -151,6 +153,11 @@ import_file cfg path = do migrate_and_import m = dbmigrate m >> dbimport m importer + | dtd == "Auto_Racing_Schedule_XML.dtd" = do + let m = unpickleDoc AutoRacingSchedule.pickle_message xml + let errmsg = "Could not unpickle Auto_Racing_Schedule_XML." + maybe (return $ ImportFailed errmsg) migrate_and_import m + | dtd == "injuriesxml.dtd" = do let m = unpickleDoc Injuries.pickle_message xml let errmsg = "Could not unpickle injuriesxml." diff --git a/src/TSN/XML/AutoRacingSchedule.hs b/src/TSN/XML/AutoRacingSchedule.hs new file mode 100644 index 0000000..62202dd --- /dev/null +++ b/src/TSN/XML/AutoRacingSchedule.hs @@ -0,0 +1,472 @@ +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} + +-- | Parse TSN XML for the DTD +-- \"Auto_Racing_Schedule_XML.dtd\". There's a top-level +-- \, containing \s, containing \, +-- containing \s. +-- +module TSN.XML.AutoRacingSchedule ( + pickle_message, + -- * Tests + auto_racing_schedule_tests, + -- * WARNING: these are private but exported to silence warnings + AutoRacingScheduleConstructor(..), + AutoRacingScheduleListingConstructor(..), + AutoRacingScheduleListingRaceResultRaceResultListingConstructor(..) ) +where + +-- System imports. +import Control.Monad ( forM_ ) +import Data.Time ( UTCTime(..) ) +import Data.Tuple.Curry ( uncurryN ) +import Database.Groundhog ( + countAll, + executeRaw, + migrate, + runMigration, + silentMigrationLogger ) +import Database.Groundhog.Core ( DefaultKey ) +import Database.Groundhog.Generic ( runDbConn ) +import Database.Groundhog.Sqlite ( withSqliteConn ) +import Database.Groundhog.TH ( + groundhog, + mkPersist ) +import Test.Tasty ( TestTree, testGroup ) +import Test.Tasty.HUnit ( (@?=), testCase ) +import Text.XML.HXT.Core ( + PU, + xp7Tuple, + xp8Tuple, + xp10Tuple, + xpElem, + xpInt, + xpList, + xpOption, + xpText, + xpWrap ) + +-- Local imports. +import TSN.Codegen ( + tsn_codegen_config ) +import TSN.DbImport ( DbImport(..), ImportResult(..), run_dbmigrate ) +import TSN.Picklers ( xp_date, xp_tba_time, xp_time_stamp ) +import TSN.XmlImport ( XmlImport(..), XmlImportFk(..) ) +import Xml ( + FromXml(..), + FromXmlFk(..), + ToDb(..), + pickle_unpickle, + unpickleable, + unsafe_unpickle ) + + +-- +-- DB/XML data types +-- + +-- * AutoRacingSchedule/Message + +-- | Database representation of a 'Message'. +-- +data AutoRacingSchedule = + AutoRacingSchedule { + db_xml_file_id :: Int, + db_heading :: String, + db_category :: String, + db_sport :: String, + db_title :: String, + db_complete_through :: String, + db_time_stamp :: UTCTime } + deriving (Eq, Show) + + +-- | XML Representation of an 'AutoRacingSchedule'. +-- +data Message = + Message { + xml_xml_file_id :: Int, + xml_heading :: String, + xml_category :: String, + xml_sport :: String, + xml_title :: String, + xml_complete_through :: String, + xml_listings :: [AutoRacingScheduleListingXml], + xml_time_stamp :: UTCTime } + deriving (Eq, Show) + +instance ToDb Message where + type Db Message = AutoRacingSchedule + +instance FromXml Message where + from_xml Message{..} = + AutoRacingSchedule { + db_xml_file_id = xml_xml_file_id, + db_heading = xml_heading, + db_category = xml_category, + db_sport = xml_sport, + db_title = xml_title, + db_complete_through = xml_complete_through, + db_time_stamp = xml_time_stamp } + +instance XmlImport Message + + +-- * AutoRacingScheduleListing/AutoRacingScheduleListingXml + +-- | Database representation of a \ contained within a +-- \. We combine the race date/time into a single +-- race_time, drop the race results list, and add a foreign key to +-- our parent. +data AutoRacingScheduleListing = + AutoRacingScheduleListing { + db_auto_racing_schedules_id :: DefaultKey AutoRacingSchedule, + db_race_id :: Int, + db_race_time :: UTCTime, + db_race_name :: String, + db_track_name :: String, + db_location :: String, + db_tv_listing :: Maybe String, + db_laps :: Int, + db_track_length :: String -- ^ Sometimes the word "miles" shows up. + } + +-- | XML representation of a \ contained within a +-- \. +-- +data AutoRacingScheduleListingXml = + AutoRacingScheduleListingXml { + xml_race_id :: Int, + xml_race_date :: UTCTime, + xml_race_time :: Maybe UTCTime, + xml_race_name :: String, + xml_track_name :: String, + xml_location :: String, + xml_tv_listing :: Maybe String, + xml_laps :: Int, + xml_track_length :: String, -- ^ Sometimes the word \"miles\" shows up. + xml_race_results :: [AutoRacingScheduleListingRaceResult] } + deriving (Eq, Show) + +-- | Pseudo-accessor to get the race result listings out of a +-- 'AutoRacingScheduleListingXml'. +result_listings :: AutoRacingScheduleListingXml + -> [AutoRacingScheduleListingRaceResultRaceResultListingXml] +result_listings = (concatMap xml_race_result_listing) . xml_race_results + + +instance ToDb AutoRacingScheduleListingXml where + type Db AutoRacingScheduleListingXml = AutoRacingScheduleListing + +instance FromXmlFk AutoRacingScheduleListingXml where + type Parent AutoRacingScheduleListingXml = AutoRacingSchedule + + from_xml_fk fk AutoRacingScheduleListingXml{..} = + AutoRacingScheduleListing { + db_auto_racing_schedules_id = fk, + db_race_id = xml_race_id, + db_race_time = make_race_time xml_race_date xml_race_time, + db_race_name = xml_race_name, + db_track_name = xml_track_name, + db_location = xml_location, + db_tv_listing = xml_tv_listing, + db_laps = xml_laps, + db_track_length = xml_track_length } + where + -- Take the day part from one, the time from the other. + make_race_time d Nothing = d + make_race_time d (Just t) = UTCTime (utctDay d) (utctDayTime t) + +instance XmlImportFk AutoRacingScheduleListingXml + +-- * AutoRacingScheduleListingRaceResult + +-- | The XML representation of \ -> \ -> +-- \. This element serves only to contain +-- \s, so we don't store the intermediate table +-- in the database. +-- +newtype AutoRacingScheduleListingRaceResult = + AutoRacingScheduleListingRaceResult { + xml_race_result_listing :: + [AutoRacingScheduleListingRaceResultRaceResultListingXml] } + deriving (Eq, Show) + +-- * AutoRacingScheduleListingRaceResultRaceResultListing / +-- AutoRacingScheduleListingRaceResultRaceResultListingXml + +data AutoRacingScheduleListingRaceResultRaceResultListing = + AutoRacingScheduleListingRaceResultRaceResultListing { + db_auto_racing_schedules_listings_id :: + DefaultKey AutoRacingScheduleListing, + db_finish_position :: Int, + db_driver_id :: Int, + db_name :: String, + db_leading_laps :: Int, + db_listing_laps :: Int, -- Avoid clash with race's "laps" field. + db_earnings :: String, -- Should be an int, but they use commas. + db_status :: String } + +data AutoRacingScheduleListingRaceResultRaceResultListingXml = + AutoRacingScheduleListingRaceResultRaceResultListingXml { + xml_finish_position :: Int, + xml_driver_id :: Int, + xml_name :: String, + xml_leading_laps :: Int, + xml_listing_laps :: Int, -- Avoid clash with race's "laps" field. + xml_earnings :: String, -- Should be an int, but they use commas. + xml_status :: String } + deriving (Eq, Show) + +instance ToDb AutoRacingScheduleListingRaceResultRaceResultListingXml where + type Db AutoRacingScheduleListingRaceResultRaceResultListingXml = + AutoRacingScheduleListingRaceResultRaceResultListing + +instance FromXmlFk AutoRacingScheduleListingRaceResultRaceResultListingXml where + type Parent AutoRacingScheduleListingRaceResultRaceResultListingXml = + AutoRacingScheduleListing + + from_xml_fk fk AutoRacingScheduleListingRaceResultRaceResultListingXml{..} = + AutoRacingScheduleListingRaceResultRaceResultListing { + db_auto_racing_schedules_listings_id = fk, + db_finish_position = xml_finish_position, + db_driver_id = xml_driver_id, + db_name = xml_name, + db_leading_laps = xml_leading_laps, + db_listing_laps = xml_listing_laps, + db_earnings = xml_earnings, + db_status = xml_earnings } + +instance XmlImportFk AutoRacingScheduleListingRaceResultRaceResultListingXml + +--- +--- Database stuff. +--- + +instance DbImport Message where + dbmigrate _ = + run_dbmigrate $ do + migrate (undefined :: AutoRacingSchedule) + migrate (undefined :: AutoRacingScheduleListing) + migrate (undefined + :: AutoRacingScheduleListingRaceResultRaceResultListing) + + dbimport m = do + msg_id <- insert_xml m + + forM_ (xml_listings m) $ \listing -> do + listing_id <- insert_xml_fk msg_id listing + + mapM_ (insert_xml_fk_ listing_id) (result_listings listing) + + return ImportSucceeded + + +mkPersist tsn_codegen_config [groundhog| +- entity: AutoRacingSchedule + dbName: auto_racing_schedules + constructors: + - name: AutoRacingSchedule + uniques: + - name: unique_auto_racing_schedule + type: constraint + # Prevent multiple imports of the same message. + fields: [db_xml_file_id] + +- entity: AutoRacingScheduleListing + dbName: auto_racing_schedules_listings + constructors: + - name: AutoRacingScheduleListing + fields: + - name: db_auto_racing_schedules_id + reference: + onDelete: cascade + +- entity: AutoRacingScheduleListingRaceResultRaceResultListing + dbName: auto_racing_schedules_listings_race_result_listings + constructors: + - name: AutoRacingScheduleListingRaceResultRaceResultListing + fields: + - name: db_auto_racing_schedules_listings_id + reference: + onDelete: cascade +|] + + + +--- +--- Pickling +--- + +-- | Pickler for the top-level 'Message'. +-- +pickle_message :: PU Message +pickle_message = + xpElem "message" $ + xpWrap (from_tuple, to_tuple) $ + xp8Tuple (xpElem "XML_File_ID" xpInt) + (xpElem "heading" xpText) + (xpElem "category" xpText) + (xpElem "sport" xpText) + (xpElem "Title" xpText) + (xpElem "Complete_Through" xpText) + (xpList pickle_listing) + (xpElem "time_stamp" xp_time_stamp) + where + from_tuple = uncurryN Message + to_tuple m = (xml_xml_file_id m, + xml_heading m, + xml_category m, + xml_sport m, + xml_title m, + xml_complete_through m, + xml_listings m, + xml_time_stamp m) + + +pickle_listing :: PU AutoRacingScheduleListingXml +pickle_listing = + xpElem "Listing" $ + xpWrap (from_tuple, to_tuple) $ + xp10Tuple (xpElem "RaceID" xpInt) + (xpElem "Race_Date" xp_date) + (xpElem "Race_Time" xp_tba_time) + (xpElem "RaceName" xpText) + (xpElem "TrackName" xpText) + (xpElem "Location" xpText) + (xpElem "TV_Listing" $ xpOption xpText) + (xpElem "Laps" xpInt) + (xpElem "TrackLength" xpText) + (xpList pickle_race_results) + where + from_tuple = uncurryN AutoRacingScheduleListingXml + to_tuple m = (xml_race_id m, + xml_race_date m, + xml_race_time m, + xml_race_name m, + xml_track_name m, + xml_location m, + xml_tv_listing m, + xml_laps m, + xml_track_length m, + xml_race_results m) + +pickle_race_results :: PU AutoRacingScheduleListingRaceResult +pickle_race_results = + xpElem "RaceResults" $ + xpWrap (to_result, from_result) $ + xpList pickle_race_results_listing + where + to_result = AutoRacingScheduleListingRaceResult + from_result = xml_race_result_listing + +pickle_race_results_listing :: + PU AutoRacingScheduleListingRaceResultRaceResultListingXml +pickle_race_results_listing = + xpElem "RaceResultsListing" $ + xpWrap (from_tuple, to_tuple) $ + xp7Tuple (xpElem "FinishPosition" xpInt) + (xpElem "DriverID" xpInt) + (xpElem "Name" xpText) + (xpElem "LeadingLaps" xpInt) + (xpElem "Laps" xpInt) + (xpElem "Earnings" xpText) + (xpElem "Status" xpText) + where + from_tuple = + uncurryN AutoRacingScheduleListingRaceResultRaceResultListingXml + + to_tuple m = (xml_finish_position m, + xml_driver_id m, + xml_name m, + xml_leading_laps m, + xml_listing_laps m, + xml_earnings m, + xml_status m) + + +-- +-- Tasty Tests +-- + +-- | A list of all tests for this module. +-- +auto_racing_schedule_tests :: TestTree +auto_racing_schedule_tests = + testGroup + "AutoRacingSchedule tests" + [ test_on_delete_cascade, + test_pickle_of_unpickle_is_identity, + test_unpickle_succeeds ] + +-- | If we unpickle something and then pickle it, we should wind up +-- with the same thing we started with. WARNING: success of this +-- test does not mean that unpickling succeeded. +-- +test_pickle_of_unpickle_is_identity :: TestTree +test_pickle_of_unpickle_is_identity = testGroup "pickle-unpickle tests" + [ check "pickle composed with unpickle is the identity" + "test/xml/Auto_Racing_Schedule_XML.xml", + + check "pickle composed with unpickle is the identity (miles track length)" + "test/xml/Auto_Racing_Schedule_XML-miles-track-length.xml" ] + where + check desc path = testCase desc $ do + (expected, actual) <- pickle_unpickle pickle_message path + actual @?= expected + + +-- | Make sure we can actually unpickle these things. +-- +test_unpickle_succeeds :: TestTree +test_unpickle_succeeds = testGroup "unpickle tests" + [ check "unpickling succeeds" + "test/xml/Auto_Racing_Schedule_XML.xml", + + check "unpickling succeeds (non-int team_id)" + "test/xml/Auto_Racing_Schedule_XML-miles-track-length.xml" ] + where + check desc path = testCase desc $ do + actual <- unpickleable path pickle_message + let expected = True + actual @?= expected + + +-- | Make sure everything gets deleted when we delete the top-level +-- record. +-- +test_on_delete_cascade :: TestTree +test_on_delete_cascade = testGroup "cascading delete tests" + [ check "deleting auto_racing_schedules deletes its children" + "test/xml/Auto_Racing_Schedule_XML.xml" , + + check ("deleting auto_racing_schedules deletes its children " ++ + "(miles track length)") + "test/xml/Auto_Racing_Schedule_XML-miles-track-length.xml" ] + where + check desc path = testCase desc $ do + sched <- unsafe_unpickle path pickle_message + let a = undefined :: AutoRacingSchedule + let b = undefined :: AutoRacingScheduleListing + let c = undefined :: AutoRacingScheduleListingRaceResultRaceResultListing + + actual <- withSqliteConn ":memory:" $ runDbConn $ do + runMigration silentMigrationLogger $ do + migrate a + migrate b + migrate c + _ <- dbimport sched + -- No idea how 'delete' works, so do this instead. + executeRaw False "DELETE FROM auto_racing_schedules;" [] + count_a <- countAll a + count_b <- countAll b + count_c <- countAll c + return $ sum [count_a, count_b, count_c] + let expected = 0 + actual @?= expected diff --git a/test/TestSuite.hs b/test/TestSuite.hs index d06efa3..051d1f0 100644 --- a/test/TestSuite.hs +++ b/test/TestSuite.hs @@ -1,5 +1,6 @@ import Test.Tasty ( TestTree, defaultMain, testGroup ) +import TSN.XML.AutoRacingSchedule ( auto_racing_schedule_tests ) import TSN.XML.Heartbeat ( heartbeat_tests ) import TSN.XML.Injuries ( injuries_tests ) import TSN.XML.InjuriesDetail ( injuries_detail_tests ) @@ -10,7 +11,8 @@ import TSN.XML.Weather ( weather_tests ) tests :: TestTree tests = testGroup "All tests" - [ heartbeat_tests, + [ auto_racing_schedule_tests, + heartbeat_tests, injuries_tests, injuries_detail_tests, news_tests, diff --git a/test/shell/import-duplicates.test b/test/shell/import-duplicates.test index afce3e5..7bf5456 100644 --- a/test/shell/import-duplicates.test +++ b/test/shell/import-duplicates.test @@ -15,15 +15,15 @@ rm -f shelltest.sqlite3 # Heartbeat.xml that doesn't really count. find ./test/xml -name '*.xml' | wc -l >>> -12 +14 >>>= 0 # Run the imports again; we should get complaints about the duplicate -# xml_file_ids. There are 2 errors for each violation, so we expect 2*11 +# xml_file_ids. There are 2 errors for each violation, so we expect 2*13 # occurrences of the string 'ERROR'. ./dist/build/htsn-import/htsn-import -c 'shelltest.sqlite3' test/xml/*.xml 2>&1 | grep ERROR | wc -l >>> -22 +26 >>>= 0 # Finally, clean up after ourselves. diff --git a/test/xml/Auto_Racing_Schedule_XML.dtd b/test/xml/Auto_Racing_Schedule_XML.dtd new file mode 100644 index 0000000..167929a --- /dev/null +++ b/test/xml/Auto_Racing_Schedule_XML.dtd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/xml/Auto_Racing_Schedule_XML.xml b/test/xml/Auto_Racing_Schedule_XML.xml new file mode 100644 index 0000000..5605102 --- /dev/null +++ b/test/xml/Auto_Racing_Schedule_XML.xml @@ -0,0 +1 @@ + 20136714 BFX%SCHEDULE Statistics NASCAR 2013 NASCAR Sprint Cup Schedule Complete through 1492 02/16/2013 08:10 PM Sprint Unlimited at Daytona Daytona International Speedway Daytona Beach, FL FOX 75 2.5 1 25 Kevin Harvick 40 75 205,075 Running 2 49 Greg Biffle 2 75 101,325 Running 3 1306 Joey Logano 0 75 61,325 Running 4 43 Tony Stewart 5 75 52,325 Running 5 28 Matt Kenseth 26 75 51,300 Running 1493 02/21/2013 02:00 PM Budweiser Duel 1 Daytona International Speedway Daytona Beach, FL SPEED 60 2.5 1 25 Kevin Harvick 23 60 57,792 Running 2 49 Greg Biffle 0 60 42,789 Running 3 59 Juan Pablo Montoya 0 60 37,789 Running 4 27 Jimmie Johnson 0 60 23,789 Running 5 18 Kurt Busch 0 60 30,789 Running 1494 02/21/2013 04:00 PM Budweiser Duel 2 Daytona International Speedway Daytona Beach, FL SPEED 60 2.5 1 304 Kyle Busch 19 60 58,977 Running 2 109 Kasey Kahne 0 60 43,963 Running 3 1321 Austin Dillon 0 60 38,963 Running 4 431 Clint Bowyer 1 60 33,963 Running 5 28 Matt Kenseth 0 60 31,963 Running 1495 02/24/2013 01:00 PM Daytona 500 Daytona International Speedway Daytona Beach, FL FOX 200 2.5 1 27 Jimmie Johnson 17 200 1,525,275 Running 2 20 Dale Earnhardt Jr. 0 200 1,104,814 Running 3 32 Mark Martin 0 200 817,013 Running 4 433 Brad Keselowski 13 200 707,855 Running 5 47 Ryan Newman 3 200 578,471 Running 1496 03/03/2013 03:00 PM Subway Fresh Fit 500 Phoenix International Raceway Avondale, AZ FOX 316 1.0 1 127 Carl Edwards 122 316 298,875 Running 2 27 Jimmie Johnson 1 316 209,686 Running 3 490 Denny Hamlin 0 316 157,575 Running 4 433 Brad Keselowski 16 316 168,076 Running 5 20 Dale Earnhardt Jr. 47 316 130,750 Running 1497 03/10/2013 03:00 PM Kobalt Tools 400 Las Vegas Motor Speedway Las Vegas, NV FOX 267 1.5 1 28 Matt Kenseth 42 267 408,666 Running 2 109 Kasey Kahne 114 267 284,340 Running 3 433 Brad Keselowski 12 267 248,956 Running 4 304 Kyle Busch 27 267 208,698 Running 5 127 Carl Edwards 0 267 180,590 Running 1498 03/17/2013 01:00 PM Food City 500 Bristol Motor Speedway Bristol, TN FOX 500 0.533 1 109 Kasey Kahne 109 500 176,760 Running 2 304 Kyle Busch 56 500 188,893 Running 3 433 Brad Keselowski 62 500 168,651 Running 4 18 Kurt Busch 1 500 134,255 Running 5 431 Clint Bowyer 0 500 144,543 Running 1499 03/24/2013 03:00 PM Auto Club 400 Auto Club Speedway Fontana, CA FOX 200 2.0 1 304 Kyle Busch 125 200 334,233 Running 2 20 Dale Earnhardt Jr. 0 200 212,340 Running 3 1306 Joey Logano 41 200 193,673 Running 4 127 Carl Edwards 0 200 181,815 Running 5 18 Kurt Busch 0 200 146,585 Running 1500 04/07/2013 01:00 PM STP Gas Booster 500 Martinsville Speedway Martinsville, VA FOX 500 0.526 1 27 Jimmie Johnson 346 500 214,471 Running 2 431 Clint Bowyer 0 500 164,893 Running 3 22 Jeff Gordon 0 500 146,446 Running 4 109 Kasey Kahne 0 500 112,385 Running 5 304 Kyle Busch 56 500 145,278 Running 1501 04/13/2013 07:30 PM NRA 500 Texas Motor Speedway Fort Worth, TX FOX 334 1.5 1 304 Kyle Busch 171 334 550,858 Running 2 266 Martin Truex Jr. 142 334 346,555 Running 3 127 Carl Edwards 0 334 268,605 Running 4 49 Greg Biffle 0 334 214,855 Running 5 1306 Joey Logano 1 334 209,913 Running 1502 04/21/2013 01:00 PM STP 400 Kansas Speedway Kansas City, KS FOX 267 1.5 1 28 Matt Kenseth 163 267 263,816 Running 2 109 Kasey Kahne 0 267 182,085 Running 3 27 Jimmie Johnson 9 267 185,021 Running 4 266 Martin Truex Jr. 46 267 155,985 Running 5 431 Clint Bowyer 0 267 156,218 Running 1503 04/27/2013 07:30 PM Toyota Owners 400 Richmond International Raceway Richmond, VA FOX 406 0.75 1 25 Kevin Harvick 3 406 242,511 Running 2 431 Clint Bowyer 113 406 196,668 Running 3 1306 Joey Logano 0 406 161,618 Running 4 59 Juan Pablo Montoya 67 406 147,474 Running 5 16 Jeff Burton 7 406 118,435 Running 1504 05/05/2013 01:00 PM Aaron's 499 Talladega Superspeedway Talladega, AL FOX 192 2.66 1 455 David Ragan 4 192 373,108 Running 2 399 David Gilliland 0 192 235,153 Running 3 127 Carl Edwards 5 192 207,720 Running 4 46 Michael Waltrip 0 192 155,620 Running 5 27 Jimmie Johnson 16 192 181,426 Running 1505 05/11/2013 07:00 PM Bojangles Southern 500 Darlington Raceway Darlington, SC FOX 367 1.366 1 28 Matt Kenseth 17 367 314,866 Running 2 490 Denny Hamlin 0 367 211,465 Running 3 22 Jeff Gordon 16 367 200,026 Running 4 27 Jimmie Johnson 0 367 178,876 Running 5 25 Kevin Harvick 0 367 165,976 Running 1506 05/18/2013 07:30 PM Sprint Showdown Charlotte Motor Speedway Concord, NC SPEED 40 1.5 1 34 Jamie McMurray 40 40 50,915 Running 2 1341 Ricky Stenhouse Jr. 0 40 40,915 Running 3 16 Jeff Burton 0 40 36,640 Running 4 310 Paul Menard 0 40 34,540 Running 5 266 Martin Truex Jr. 0 40 33,540 Running 1507 05/18/2013 09:00 PM NASCAR Sprint All-Star Race Charlotte Motor Speedway Concord, NC SPEED 90 1.5 1 27 Jimmie Johnson 10 90 1,039,175 Running 2 1306 Joey Logano 0 90 244,175 Running 3 304 Kyle Busch 29 90 144,175 Running 4 109 Kasey Kahne 11 90 114,150 Running 5 18 Kurt Busch 29 90 109,150 Running 1508 05/26/2013 06:00 PM Coca-Cola 600 Charlotte Motor Speedway Concord, NC FOX 400 1.5 1 25 Kevin Harvick 28 400 407,011 Running 2 109 Kasey Kahne 161 400 291,615 Running 3 18 Kurt Busch 8 400 218,560 Running 4 490 Denny Hamlin 6 400 220,915 Running 5 1306 Joey Logano 0 400 174,823 Running 1509 06/02/2013 01:00 PM FedEx 400 Dover International Speedway Dover, DE FOX 400 1.0 1 43 Tony Stewart 139 400 318,100 Running 2 59 Juan Pablo Montoya 19 400 226,504 Running 3 22 Jeff Gordon 0 400 203,051 Running 4 304 Kyle Busch 150 400 196,198 Running 5 433 Brad Keselowski 2 400 177,431 Running 1510 06/09/2013 01:00 PM Party in the Poconos 400 Pocono Raceway Long Pond, PA TNT 160 2.5 1 27 Jimmie Johnson 128 160 244,436 Running 2 49 Greg Biffle 0 160 179,185 Running 3 20 Dale Earnhardt Jr. 0 160 143,060 Running 4 43 Tony Stewart 0 160 165,810 Running 5 47 Ryan Newman 19 160 144,343 Running 1511 06/16/2013 01:00 PM Quicken Loans 400 Michigan International Speedway Brooklyn, MI TNT 200 2.0 1 49 Greg Biffle 48 200 240,460 Running 2 25 Kevin Harvick 0 200 173,781 Running 3 266 Martin Truex Jr. 0 200 145,670 Running 4 304 Kyle Busch 0 200 148,803 Running 5 43 Tony Stewart 0 200 142,085 Running 1513 06/23/2013 03:00 PM Toyota/Save Mart 350 Sonoma Raceway Sonoma, CA TNT 110 1.99 1 266 Martin Truex Jr. 51 110 309,250 Running 2 22 Jeff Gordon 4 110 240,451 Running 3 127 Carl Edwards 0 110 198,140 Running 4 18 Kurt Busch 15 110 154,860 Running 5 431 Clint Bowyer 0 110 157,548 Running 1514 06/30/2013 12:00 PM Quaker State 400 Kentucky Speedway Sparta, KY TNT 267 1.5 1 28 Matt Kenseth 38 267 200,451 Running 2 34 Jamie McMurray 0 267 159,115 Running 3 431 Clint Bowyer 0 267 146,078 Running 4 1306 Joey Logano 0 267 130,338 Running 5 304 Kyle Busch 0 267 139,743 Running 1515 07/06/2013 07:30 PM Coke Zero 400 Daytona International Speedway Daytona Beach, FL TNT 161 2.5 1 27 Jimmie Johnson 94 161 327,961 Running 2 43 Tony Stewart 0 161 254,490 Running 3 25 Kevin Harvick 0 161 224,301 Running 4 431 Clint Bowyer 0 161 182,073 Running 5 46 Michael Waltrip 0 161 141,365 Running 1516 07/14/2013 01:00 PM Camping World RV Sales 301 New Hampshire Motor Speedway Loudon, NH TNT 302 1.058 1 118 Brian Vickers 16 302 214,075 Running 2 304 Kyle Busch 53 302 228,043 Running 3 16 Jeff Burton 0 302 147,135 Running 4 433 Brad Keselowski 14 302 179,076 Running 5 580 Aric Almirola 0 302 152,496 Running 1517 07/28/2013 01:00 PM Crown Royal 400 at the Brickyard Indianapolis Motor Speedway Indianapolis, IN ESPN 160 2.5 1 47 Ryan Newman 45 160 423,033 Running 2 27 Jimmie Johnson 73 160 379,736 Running 3 109 Kasey Kahne 0 160 299,500 Running 4 43 Tony Stewart 0 160 300,650 Running 5 28 Matt Kenseth 0 160 273,266 Running 1518 08/04/2013 01:00 PM GoBowling.com 400 Pocono Raceway Long Pond, PA ESPN 160 2.5 1 109 Kasey Kahne 66 160 208,500 Running 2 22 Jeff Gordon 7 160 199,221 Running 3 18 Kurt Busch 9 160 153,930 Running 4 47 Ryan Newman 2 160 161,343 Running 5 20 Dale Earnhardt Jr. 2 160 130,585 Running 1519 08/11/2013 01:00 PM Cheez-It 355 at the Glen Watkins Glen International Watkins Glen, NY ESPN 90 2.45 1 304 Kyle Busch 29 90 236,658 Running 2 433 Brad Keselowski 0 90 204,876 Running 3 266 Martin Truex Jr. 0 90 161,735 Running 4 127 Carl Edwards 0 90 149,360 Running 5 59 Juan Pablo Montoya 1 90 137,524 Running 1520 08/18/2013 01:00 PM Pure Michigan 400 Michigan International Speedway Brooklyn, MI ESPN 200 2.0 1 1306 Joey Logano 51 200 252,393 Running 2 25 Kevin Harvick 0 200 180,731 Running 3 18 Kurt Busch 43 200 136,315 Running 4 310 Paul Menard 0 200 143,486 Running 5 431 Clint Bowyer 0 200 145,493 Running 1521 08/24/2013 07:30 PM IRWIN Tools Night Race Bristol Motor Speedway Bristol, TN ABC 500 0.533 1 28 Matt Kenseth 149 500 328,466 Running 2 109 Kasey Kahne 0 500 214,815 Running 3 59 Juan Pablo Montoya 0 500 200,529 Running 4 118 Brian Vickers 0 500 150,315 Running 5 1306 Joey Logano 0 500 155,973 Running 1522 09/01/2013 07:30 PM AdvoCare 500 Atlanta Motor Speedway Hampton, GA ESPN 325 1.54 1 304 Kyle Busch 36 325 338,058 Running 2 1306 Joey Logano 78 325 244,473 Running 3 266 Martin Truex Jr. 0 325 207,065 Running 4 18 Kurt Busch 0 325 165,235 Running 5 47 Ryan Newman 3 325 167,848 Running 1523 09/07/2013 07:30 PM Federated Auto Parts 400 Richmond International Raceway Richmond, VA ABC 400 0.75 1 127 Carl Edwards 46 400 286,475 Running 2 18 Kurt Busch 73 400 185,355 Running 3 47 Ryan Newman 4 400 181,443 Running 4 34 Jamie McMurray 6 400 151,805 Running 5 310 Paul Menard 3 400 140,701 Running 1524 09/15/2013 02:00 PM GEICO 400 Chicagoland Speedway Joliet, IL ESPN 267 1.5 1 28 Matt Kenseth 89 267 334,891 Running 2 304 Kyle Busch 67 267 261,048 Running 3 25 Kevin Harvick 2 267 221,326 Running 4 18 Kurt Busch 0 267 169,960 Running 5 27 Jimmie Johnson 40 267 176,926 Running 1525 09/22/2013 02:00 PM Sylvania 300 New Hampshire Motor Speedway Loudon, NH ESPN 300 1.058 1 28 Matt Kenseth 106 300 262,066 Running 2 304 Kyle Busch 0 300 210,143 Running 3 49 Greg Biffle 0 300 151,785 Running 4 27 Jimmie Johnson 1 300 160,796 Running 5 34 Jamie McMurray 0 300 142,005 Running 1526 09/29/2013 02:00 PM AAA 400 Dover International Speedway Dover, DE ESPN 400 1.0 1 27 Jimmie Johnson 243 400 243,836 Running 2 20 Dale Earnhardt Jr. 80 400 197,210 Running 3 1306 Joey Logano 0 400 166,068 Running 4 22 Jeff Gordon 3 400 168,296 Running 5 304 Kyle Busch 30 400 162,068 Running 1527 10/06/2013 02:00 PM Hollywood Casino 400 Kansas Speedway Kansas City, KS ESPN 267 1.5 1 25 Kevin Harvick 138 267 364,636 Running 2 18 Kurt Busch 0 267 229,810 Running 3 22 Jeff Gordon 0 267 216,776 Running 4 1306 Joey Logano 33 267 176,473 Running 5 127 Carl Edwards 0 267 169,965 Running 1528 10/12/2013 07:30 PM Bank of America 500 Charlotte Motor Speedway Concord, NC ABC 334 1.5 1 433 Brad Keselowski 11 334 314,441 Running 2 109 Kasey Kahne 138 334 224,810 Running 3 28 Matt Kenseth 1 334 199,426 Running 4 27 Jimmie Johnson 130 334 192,721 Running 5 304 Kyle Busch 4 334 163,568 Running 1529 10/20/2013 02:00 PM Camping World RV Sales 500 Talladega Superspeedway Talladega, AL ESPN 188 2.66 1 34 Jamie McMurray 16 188 236,345 Running 2 20 Dale Earnhardt Jr. 38 188 185,410 Running 3 1341 Ricky Stenhouse Jr. 6 188 187,596 Running 4 310 Paul Menard 0 188 154,726 Running 5 304 Kyle Busch 9 188 162,068 Running 1530 10/27/2013 02:00 PM Goody's Headache Relief Shot 500 Martinsville Speedway Ridgeway, VA ESPN 500 0.526 1 22 Jeff Gordon 78 500 188,796 Running 2 28 Matt Kenseth 202 500 177,736 Running 3 431 Clint Bowyer 60 500 141,478 Running 4 433 Brad Keselowski 0 500 153,436 Running 5 27 Jimmie Johnson 123 500 144,046 Running 1531 11/03/2013 03:00 PM AAA Texas 500 Texas Motor Speedway Fort Worth, TX ESPN 334 1.5 1 27 Jimmie Johnson 255 334 484,211 Running 2 20 Dale Earnhardt Jr. 0 334 337,810 Running 3 1306 Joey Logano 1 334 256,393 Running 4 28 Matt Kenseth 3 334 238,776 Running 5 109 Kasey Kahne 0 334 180,585 Running 1532 11/10/2013 03:00 PM AdvoCare 500 Phoenix International Raceway Avondale, AZ ESPN 312 1.0 1 25 Kevin Harvick 70 312 263,386 Running 2 109 Kasey Kahne 41 312 171,715 Running 3 27 Jimmie Johnson 1 312 182,326 Running 4 20 Dale Earnhardt Jr. 2 312 131,135 Running 5 18 Kurt Busch 0 312 137,630 Running 1533 11/17/2013 03:00 PM Ford EcoBoost 400 Homestead-Miami Speedway Homestead, FL ESPN 267 1.5 1 490 Denny Hamlin 72 267 322,350 Running 2 28 Matt Kenseth 144 267 293,251 Running 3 20 Dale Earnhardt Jr. 28 267 203,860 Running 4 266 Martin Truex Jr. 0 267 179,435 Running 5 431 Clint Bowyer 0 267 167,968 Running January 13, 2014, at 11:34 AM ET \ No newline at end of file -- 2.43.2