From e719c5f34fe7572ef46e63a16a0766d956ebb794 Mon Sep 17 00:00:00 2001 From: JJ Merelo Date: Sun, 29 Sep 2019 08:13:37 +0200 Subject: [PATCH] Adapts and eliminates unneeded tests --- README.md | 113 +------------------ t/010-cached.t | 123 -------------------- t/020-source.t | 240 ---------------------------------------- t/030-sub-directories.t | 39 ------- t/040-pod-extraction.t | 67 ----------- t/100-heavy-duty.t | 56 ---------- 6 files changed, 5 insertions(+), 633 deletions(-) delete mode 100644 t/010-cached.t delete mode 100644 t/020-source.t delete mode 100644 t/030-sub-directories.t delete mode 100644 t/040-pod-extraction.t delete mode 100644 t/100-heavy-duty.t diff --git a/README.md b/README.md index 5c36aba..4113095 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,19 @@ -# Pod::To::Cached +# CompUnit::PrecompilationRepository::Document [![Build Status](https://travis-ci.com/perl6/Pod-To-Cached.svg?branch=master)](https://travis-ci.com/perl6/Pod-To-Cached) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/perl6/Pod-To-Cached?svg=true)](https://ci.appveyor.com/api/projects/status/github/perl6/Pod-To-Cached?svg=true) -Create and Maintain a cache of precompiled pod files - -Module to take a collection of pod files and create a precompiled cache. Methods / functions -to add a pod file to a cache. +This module has been spun off from Pod::To::Cached, and will deal with the cache of a single file. ## Install This module is in the [Perl 6 ecosystem](https://modules.perl6.org), so you install it in the usual way: - zef install Pod::To::Cached - - -# SYNOPSIS -```perl6 -use Pod::To::Cached; - -my Pod::To::Cached $cache .= new(:path, :source); - -$cache.update-cache; - -for $cache.hash-files.kv -> $source-name, $status { - given $status { - when 'Current' {say "「$source-name」 is up to date with POD source"} - when 'Valid' {say "「$source-name」 has valid POD, but newer POD source contains invalid POD"} - when 'Failed' {say "「$source-name」 is not in cache, and source file contains invalid POD"} - when 'New' { say "「$source-name」 is not in cache and cache has not been updated"} - when 'Old' { say "「$source-name」 is in cache, but has no associated pod file in DOC"} - } - user-supplied-routine-for-processing-pod( $cache.pod( $source-name ) ); -} - -# Find files with status -say 'These pod files failed:'; -.say for $cache.list-files( 'Failed' ); -say 'These sources have valid pod:'; -.say for $cache.list-files(); - -# Find date when pod added to cache -my $source = 'language/pod'; # name of a documentation source -say "「$source」 was added on 「{ $cache.cache-timestamp( $source ) }」"; - -# Remove the dependence on the pod source -$cache.freeze; - -``` -## Notes -- Str $!path = '.pod6-cache' - path to the directory where the cache will be created/kept - -- Str $!source = 'doc' - path to the collection of pod files - ignored if cache frozen - -- @!extensions = - the possible extensions for a POD file - -- verbose = False - Whether processing information is sent to stderr. - -- new - Instantiates class. On instantiation, - - get the cache from index, or creates a new cache if one does not exist - - if frozen, does not check the source directory - - if not frozen, or new cache being created, verifies - - source directory exists - - the source directory contains POD/POD6 etc files (recursively) - - no duplicate pod file names exist, eg. xx.pod & xx.pod6 - - verifies whether the cache is valid - -- update-cache - All files with a modified timestamp (reported by the filesystem) after the added instant are precompiled and added to the cache - - Status is changed to Updated (compiles Valid) or Fail (does not compile) - - Failed files that were previously Valid files still retain the old cache handle - - Throws an exception when called on a frozen cache - -- freeze - Can be called only when there are only Valid or Updated (no New, Tainted or Failed files), - otherwise dies. - The intent of this method is to allow the pod-cache to be copied without the original pod-files. - update-cache will throw an error if used on a frozen cache - -- list-files( Str $s --> Positional ) - returns an Sequence of files with the given status - -- list-files( Str $s1, $s2 --> Positional ) - returns an Array of files with the given status list - -- hash-files( *@statuses? --> Associative ) - returns a map of the source-name and its statuses - - explicitly give required status strings: C<< $cache.hash-files() >> - - return all files C< $cache.hash-files > - -- cache-timestamp( $source --> Instant ) - returns the Instant when a valid version of the Pod was added to the cache - - if the time-stamp is before the time the Pod was modified, then the pod has errors - - a Failed source has a timestamp of zero + zef install CompUnit::PrecompilationRepository::Document -- pod - - method pod(Str $source) - - Returns an array of POD Objects generated from the file associated with $source name. - - When a doc-set is being actively updated, then pod files may have failed, in which case they have Status Valid. - - To froze a cache, all files must have Current status +## Author -- Status is an enum with the following elements and semantics - - Current - There is a compiled source in the cache with an added date **after** the modified date - - Valid - There is a compiled source in the cache with an added date **before** the modified date and there has been an attempt to add the source to cache that did not compile - - Failed - There is not a compiled source in the cache, but there has been an attempt to add the source name to the cache that did not compile - - New - A new pod source has been detected that is not in cache, but C has not yet been called to compile the source. A transitional Status - - Old - A source name that is in the cache but no longer reflects an existing source. +This module was created originaly by [Richard Hainsworth](https://github.com/finanalyst) ## LICENSE diff --git a/t/010-cached.t b/t/010-cached.t deleted file mode 100644 index 1a0cb38..0000000 --- a/t/010-cached.t +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env perl6 -use lib 'lib'; -use Test; -use File::Directory::Tree; -use Pod::To::Cached; - -constant REP = 't/tmp/ref'; -constant DOC = 't/tmp/doc'; -constant INDEX = REP ~ '/file-index.json'; - -plan 8; - - -if 't/tmp'.IO ~~ :d { - empty-directory 't/tmp'; -} -else { - mktree 't/tmp' -} - -my Pod::To::Cached $cache; - -mktree REP; - -#--MARKER-- Test 1 -throws-like { $cache .= new(:source( DOC ), :path(REP)) }, - Exception, :message(/'has corrupt doc-cache'/), 'Detects absence of index file'; - -INDEX.IO.spurt(q:to/CONTENT/); - { - "frozen": "True", - files: { "one": "ONE", "two": "TWO" } - } -CONTENT - -#--MARKER-- Test 2 -throws-like { $cache .= new(:source( DOC ), :path( REP )) }, - Exception, :message(/'Configuration failed'/), 'Bad JSON in index file'; - -INDEX.IO.spurt(q:to/CONTENT/); - { - "frozen": "True", - "files": [ "one", "ONE", "two", "TWO" ], - "source": "SOURCE" - } - CONTENT -#--MARKER-- Test 3 -throws-like { $cache .= new(:source( DOC ), :path( REP )) }, - Exception, :message(/'Invalid index file'/), 'Files not hash'; - -INDEX.IO.spurt(q:to/CONTENT/); - { - "files": { "one": "ONE", "two": "TWO" }, - "source": "SOURCE" - } - CONTENT -#--MARKER-- Test 4 -throws-like { $cache .= new(:source( DOC ), :path( REP )) }, - Exception, :message(/'Invalid index file'/), 'No frozen'; - -INDEX.IO.spurt(q:to/CONTENT/); - { - "frozen": "True", - "source": "SOURCE" - } - CONTENT -#--MARKER-- Test 5 -throws-like { $cache .= new(:source( DOC ), :path( REP )) }, - Exception, :message(/'Invalid index file'/), 'No files'; - -INDEX.IO.spurt(q:to/CONTENT/); - { - "frozen": "False", - "files": { - "one": { - "cache-key": "ONE", - "added": 10, - "path": "some/path", - "status": "Valid" - }, - "two": { - "cache-key": "TWO", - "added": 10, - "path": "some/path", - "status": "Valid" - } - } - } - CONTENT -#--MARKER-- Test 6 -throws-like { $cache .= new(:source( DOC ), :path( REP )) }, - Exception, :message(/'Invalid index file'/), 'No source without frozen'; - -INDEX.IO.spurt(q:to/CONTENT/); - { - "frozen": "False", - "files": { - "one": { - "cache-key": "ONE", - "added": 10, - "path": "some/path", - "status": "Valid" - }, - "two": { - "cache-key": "TWO", - "added": 10, - "path": "some/path", - "status": "Valid" - } - }, - "source": "t/tmp/doc" - } - CONTENT -#--MARKER-- Test 7 -throws-like { $cache .= new(:source( DOC ), :path( REP )) }, - Exception, :message(/'Source verification failed'/), 'No source directory at source in index'; - -# TODO source-verify with frozen cache -rmtree REP ; - -#--MARKER-- Test 8 -throws-like { $cache .= new(:source( DOC ), :path( REP )) }, - Exception, :message(/'is not a directory'/), 'Detects absence of source directory'; \ No newline at end of file diff --git a/t/020-source.t b/t/020-source.t deleted file mode 100644 index ab06873..0000000 --- a/t/020-source.t +++ /dev/null @@ -1,240 +0,0 @@ -use lib 'lib'; # -*- mode: perl6 -*- -use Test; -use Test::Output; -use File::Directory::Tree; -use JSON::Fast; -use Pod::To::Cached; - -constant REP = 't/tmp/ref'; -constant DOC = 't/tmp/doc'; -constant INDEX = REP ~ '/file-index.json'; - -plan 36; - -my Pod::To::Cached $cache; - -rmtree DOC if DOC.IO ~~ :d; -rmtree REP if REP.IO ~~ :d; - -mkdir IO::Path.new: DOC; - -#--MARKER-- Test 1 -throws-like { $cache .= new( :source( DOC ), :path( REP ) ) }, - Exception, :message(/'No POD files found under'/), 'Detects absence of source files'; - -(DOC ~ '/a-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =pod A test file - =TITLE This is a title - - Some text - - =end pod - POD-CONTENT - -(DOC ~ '/a-second-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =pod Another test file - =TITLE More and more - - Some more text - - =end pod - POD-CONTENT -# Change the extension but not the name -(DOC ~ '/a-second-pod-file.pod').IO.spurt(q:to/POD-CONTENT/); - =pod Another test file - =TITLE More and more - - Some more text - - =end pod - POD-CONTENT - -#--MARKER-- Test 2 -throws-like { $cache .= new( :source( DOC ), :path( REP ))}, - Exception, :message(/'duplicates name of'/), 'Detects duplication of source file names'; - -(DOC ~ '/a-second-pod-file.pod').IO.unlink ; -#--MARKER-- Test 3 -nok REP.IO ~~ :d, 'No cache directory should be created yet'; -#--MARKER-- Test 4 -lives-ok { $cache .= new( :source( DOC ), :path( REP ), :!verbose) }, 'Instantiates OK'; -#--MARKER-- Test 5 -ok REP.IO ~~ :d, 'Correctly creates the cache directory'; -#--MARKER-- Test 6 -ok INDEX.IO ~~ :f, 'index file has been created'; -my %config; -#--MARKER-- Test 7 -lives-ok { %config = from-json( INDEX.IO.slurp ) }, 'good json in index'; -#--MARKER-- Test 8 -ok (%config:exists and %config ~~ 'False'), 'frozen is False as expected'; -#--MARKER-- Test 9 -subtest "Files test", { - ok (%config:exists and %config.WHAT ~~ Hash), 'files is as expected'; -} -#--MARKER-- Test 10 -is +%config.keys, 0, 'No source files in index because nothing in cache'; - -my $mod-time = INDEX.IO.modified; -my $rv; -#--MARKER-- Test 11 -subtest "Cache updated", { - lives-ok {$rv = $cache.update-cache}, 'Updates cache without dying'; -} -#--MARKER-- Test 12 -nok $rv, 'Returned false because of compile errors'; -#--MARKER-- Test 13 -like $cache.error-messages[0], /'Compile error in'/, 'Error messages saved'; -#--MARKER-- Test 14 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Failed', 'a-second-pod-file'=>'Failed').hash, 'lists Failed files'; -#--MARKER-- Test 15 -nok INDEX.IO.modified > $mod-time, 'INDEX not modified'; -#--MARKER-- Test 16 -is +gather for $cache.files.kv -> $nm, %inf { take 'f' unless %inf:exists }, - 2, 'No handles are defined for Failed files'; - -$cache.verbose = True; -#--MARKER-- Test 17 -stderr-like { $cache.update-cache }, /'Cache not fully updated'/, 'Got correct progress message'; -$cache.verbose = False; - -(DOC ~ '/a-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =begin pod - =TITLE This is a title - - Some text - - =end pod - POD-CONTENT - -(DOC ~ '/a-second-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =begin pod - =TITLE More and more - - Some more text - - =end pod - POD-CONTENT - -#--MARKER-- Test 18 -subtest "Update cache", { - ok $cache.update-cache, 'Returned true because both POD now compile'; - %config = from-json( INDEX.IO.slurp ); # Re-reads cache index - for -> $f { - ok( %config{$f}, "Status of $f is OK and %config{$f}") - } -} -# this works because the source paths are in the cache object, and they are new - -#--MARKER-- Test 19 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Current', 'a-second-pod-file'=>'Current').hash, 'hash-files shows two pod Current'; - -#--MARKER-- Test 20 -ok INDEX.IO.modified > $mod-time, 'INDEX has been modified because updated cache ok'; - -(DOC ~ '/a-second-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); -=begin pod -=TITLE More and more - -Some more text but now it is changed - -=end pod -POD-CONTENT - -$cache .= new( :source( DOC ), :path( REP )); - -#--MARKER-- Test 21 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Current', 'a-second-pod-file'=>'Valid').hash, 'One current, one tainted'; -#--MARKER-- Test 22 -is-deeply $cache.list-files( ), ( 'a-pod-file' , 'a-second-pod-file', ), 'List with list of statuses'; -$cache.update-cache; -#--MARKER-- Test 23 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Current', 'a-second-pod-file'=>'Current').hash, 'Both updated'; - -#has an error -(DOC ~ '/a-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =pod A test file - =TITLE This is a title - - Some text - - =end pod - POD-CONTENT - -$cache .= new( :source( DOC ), :path( REP )); -#--MARKER-- Test 24 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Valid', 'a-second-pod-file'=>'Current').hash, 'One current, one tainted'; -$cache.update-cache; -#--MARKER-- Test 25 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Valid', 'a-second-pod-file'=>'Current').hash, 'One remains Valid because new version did not compile'; -#--MARKER-- Test 26 -like $cache.error-messages.join, /'Compile error'/, 'Error detected in pod'; - -#--MARKER-- Test 27 -lives-ok { $rv = $cache.cache-timestamp('a-pod-file')}, 'method cache-timestamp lives'; -#--MARKER-- Test 28 -ok $rv ~~ Instant, 'return value is an Instant'; -#--MARKER-- Test 29 -ok $rv < (DOC ~ '/a-pod-file.pod6').IO.modified, 'new file not added to cache because it did not compile'; -# return to valid pod; -(DOC ~ '/a-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =begin pod - =TITLE This is a title - - Some text - - =end pod - POD-CONTENT - -#--MARKER-- Test 30 -lives-ok {$cache .=new(:path( REP ))}, 'with a valid cache, source can be omitted'; -$cache.update-cache; -#--MARKER-- Test 31 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Current', 'a-second-pod-file'=>'Current').hash, 'Both Current now'; -#--MARKER-- Test 32 -ok (DOC ~ '/a-pod-file.pod6').IO.modified < $cache.cache-timestamp('a-pod-file'), 'new file compiles so timestamp after modification'; -(DOC ~ '/pod-file-to-deprecate.pod6').IO.spurt(q:to/POD-CONTENT/); - =begin pod - =TITLE This will be disappear from source - - Some more text but now it is changed - - =end pod - POD-CONTENT -$cache .=new(:path(REP)); -$cache.update-cache; -#--MARKER-- Test 33 -is +$cache.hash-files.keys, 3, 'file added'; -(DOC ~ '/pod-file-to-deprecate.pod6').IO.unlink; -#--MARKER-- Test 34 -stderr-like {$cache .=new(:path(REP),:verbose)}, / - 'names not associated with pod files:' - .+ 'pod-file-to-deprecate' - /, 'detects old files'; - -$cache.verbose = False; -$cache.update-cache; -#--MARKER-- Test 35 -is-deeply $cache.hash-files, ( 'a-pod-file' => 'Current', 'a-second-pod-file'=>'Current', 'pod-file-to-deprecate' => 'Old').hash, 'An Old file is detected'; -#has an error -(DOC ~ '/a-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =pod A test file - =TITLE This is a title - - Some text - - =end pod - POD-CONTENT - -$cache .= new( :source( DOC ), :path( REP )); -#--MARKER-- Test 36 -is-deeply $cache.hash-files(), %( 'a-pod-file' => 'Valid', 'pod-file-to-deprecate' => 'Old'), 'hash-files with seq of statuses correct'; - -#restore valid source file for later tests. -(DOC ~ '/a-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =begin pod - =TITLE This is a title - - Some text - - =end pod - POD-CONTENT diff --git a/t/030-sub-directories.t b/t/030-sub-directories.t deleted file mode 100644 index e22c8aa..0000000 --- a/t/030-sub-directories.t +++ /dev/null @@ -1,39 +0,0 @@ -use lib 'lib'; -use Test; -use Test::Output; -use JSON::Fast; -use Pod::To::Cached; - -constant REP = 't/tmp/ref'; -constant DOC = 't/tmp/doc'; -constant INDEX = REP ~ '/file-index.json'; - -my Pod::To::Cached $cache; -my $content = q:to/PODEND/; - =begin pod - Some text - =end pod - PODEND - -for -> $d { - my $dir = DOC ~ "/$d"; - mkdir IO::Path.new( $dir ); - ok( $dir.IO ~~ :d, "Directory $d created correctly" ); # Checks that the directories are created - ("$dir/a-file-$_.pod6").IO.spurt($content) for 1..4; - ok( "$dir/a-file-1.pod6".IO ~~ :f, "1 files created correctly" ); -} - -#--MARKER-- Test 1 -lives-ok { $cache .=new( :path( REP ), :source( DOC ) )}, 'doc cache created with sub-directories'; -$cache.update-cache; -#--MARKER-- Test 2 -lives-ok { $cache.update-cache }, 'update cache with sub-dirs'; -#--MARKER-- Test 3 -nok 'sub-dir-1' ~~ any( $cache.hash-files.keys ), 'sub-directories filtered from file list'; - - -'t'.IO.&indir( {$cache .= new(:source( 'tmp/doc' ) ) } ); -#--MARKER-- Test 4 -ok 't/.pod-cache'.IO ~~ :d, 'default repository created'; - -done-testing; diff --git a/t/040-pod-extraction.t b/t/040-pod-extraction.t deleted file mode 100644 index e3fd1ce..0000000 --- a/t/040-pod-extraction.t +++ /dev/null @@ -1,67 +0,0 @@ -use lib 'lib'; -use Test; -use Test::Output; -use Pod::To::Cached; - -constant REP = 't/tmp/ref'; -constant DOC = 't/tmp/doc'; -constant INDEX = REP ~ '/file-index.json'; - -plan 11; - -my Pod::To::Cached $cache; -my $rv; -diag 'Test pod extraction'; -rm-cache( REP ); -$cache .= new( :source( DOC ), :path( REP ), :!verbose); -$cache.update-cache; -#--MARKER-- Test 1 -ok $cache.pod('a-pod-file')[0] ~~ Pod::Block::Named, 'pod is returned from cache'; - - -(DOC ~ '/a-second-pod-file.pod6').IO.spurt(q:to/POD-CONTENT/); - =begin pod - =TITLE More and more - - Some extra changed text but now it is changed - - =end pod - POD-CONTENT - -$cache .=new(:path( REP )); -my %h = $cache.hash-files; -#--MARKER-- Test 2 -is %h, 'Valid', 'The old version is still in cache, no update-cache'; -#--MARKER-- Test 3 -lives-ok { $rv = $cache.pod('a-second-pod-file') }, 'Old Pod is provided'; - -#--MARKER-- Test 4 -like $rv[0].contents[1].contents[0], - /'Some more text but now it is changed'/, - 'previous text in source'; - -diag 'testing freeze'; -#--MARKER-- Test 5 -throws-like { $cache.freeze }, Exception, - :message(/'Cannot freeze because some files not Current'/), - 'Cannot freeze when a file not Current'; - -#--MARKER-- Test 6 -ok $cache.update-cache, 'updates without problem'; - -#--MARKER-- Test 7 -like $cache.pod('a-second-pod-file')[0].contents[1].contents[0], - /'Some more text but now it is changed'/, - 'new version after update'; - -#--MARKER-- Test 8 -lives-ok { $cache.freeze }, 'All updated so now can freeze'; - -#--MARKER-- Test 9 -lives-ok { $cache .=new(:path( REP )) }, 'Gets a frozen cache without source'; - -#--MARKER-- Test 10 -throws-like { $cache.update-cache }, Exception, :message(/ 'Cannot update frozen cache'/), 'No updating on a frozen cache'; - -#--MARKER-- Test 11 -throws-like {$cache.pod('xxxyyyzz') }, Exception, :message(/ 'Source name 「xxxyyyzz」 not in cache'/), 'Cannot get POD for invalid source name'; diff --git a/t/100-heavy-duty.t b/t/100-heavy-duty.t deleted file mode 100644 index 88bcb46..0000000 --- a/t/100-heavy-duty.t +++ /dev/null @@ -1,56 +0,0 @@ -#! /usr/bin/env perl6 -use v6.*; -use lib 'lib'; -use Test; -use Test::Output; -use File::Directory::Tree; -use Pod::To::Cached; - -diag 'Heavy duty test optional'; -done-testing; exit; # comment this line out to get test - -if 't/tmp'.IO ~~ :d { - empty-directory 't/tmp'; -} -else { - mktree 't/tmp' -} -mktree 't/tmp/doc'; - -# set up pod files -constant DRY-RUN = 5; -constant HEAVY-RUN = 70; -constant TEST-FILE = 't/doctest/community.pod6'; - -my $text = TEST-FILE.IO.slurp; -my Pod::To::Cached $cache; -my ( $start1, $start2, $stop1, $stop2); - -"t/tmp/doc/test-$_.pod6".IO.spurt($text) for ^DRY-RUN; -#--MARKER-- Test 1 -is +'t/tmp/doc'.IO.dir, DRY-RUN, 'files written'; -$start1 = now; -#--MARKER-- Test 2 -lives-ok { $cache .= new(:path, :source); $cache.update-cache }, 'dry run lives'; -$stop1 = now; -#--MARKER-- Test 3 -is +$cache.list-files( 'Current' ), DRY-RUN, DRY-RUN ~ ' files cached as compiled'; - -empty-directory 't/tmp'; -mktree 't/tmp/doc'; - -diag 'Starting Heavy run'; -"t/tmp/doc/test-$_.pod6".IO.spurt($text) for ^HEAVY-RUN; -#--MARKER-- Test 4 -is +'t/tmp/doc'.IO.dir, HEAVY-RUN, 'files written'; -$start2 = now; -$cache .= new(:path, :source); -$cache.update-cache ; -$stop2 = now; -#--MARKER-- Test 5 -is +$cache.list-files( 'Current' ), HEAVY-RUN, HEAVY-RUN ~ ' files cached as compiled'; - -diag "Dry run took " ~ DateTime.new($stop1 - $start1).hh-mm-ss ~ '. Heavy run took ' ~ DateTime.new($stop2-$start2).hh-mm-ss; -empty-directory 't/tmp'; - -done-testing; \ No newline at end of file