From 36003eded9d74287b6404a1426ea114e3b8f792a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Fri, 13 Feb 2026 23:41:34 -0700 Subject: [PATCH] fix: re-save compiled template to disk when source changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When COMPILE_DIR/COMPILE_EXT is configured, the first compile of a template correctly writes the compiled Perl code to disk. However, when the template source file is later modified and _refresh() recompiles it, the call to _compile() was missing the compiled filename argument. This meant the recompiled template was only stored in memory, never persisted to disk. On the next cold start, the stale compiled file would be loaded instead of the updated source, leading to incorrect template output until the compiled file was manually deleted. Fix: pass _compiled_filename() to _compile() in _refresh(), matching the behavior already used in _fetch(). Resolves: https://github.com/abw/Template2/issues/98 See also: https://github.com/abw/Template2/pull/197 Co-Authored-By: Kōan --- lib/Template/Provider.pm | 2 +- t/compile6.t | 116 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 t/compile6.t diff --git a/lib/Template/Provider.pm b/lib/Template/Provider.pm index 3fe798b1..b55a6dba 100644 --- a/lib/Template/Provider.pm +++ b/lib/Template/Provider.pm @@ -711,7 +711,7 @@ sub _refresh { if $self->{ DEBUG }; ($data, $error) = $self->_load($slot->[ NAME ], $slot->[ DATA ]->{ name }); - ($data, $error) = $self->_compile($data) + ($data, $error) = $self->_compile($data, $self->_compiled_filename($slot->[ NAME ])) unless $error; if ($error) { diff --git a/t/compile6.t b/t/compile6.t new file mode 100644 index 00000000..c6ce8a7b --- /dev/null +++ b/t/compile6.t @@ -0,0 +1,116 @@ +#============================================================= -*-perl-*- +# +# t/compile6.t +# +# Test that when a template source file changes on disk and is +# recompiled via _refresh(), the compiled file on disk is also +# updated (not just the in-memory cache). +# +# This is a regression test for the bug described in: +# https://github.com/abw/Template2/issues/98 +# https://github.com/abw/Template2/pull/197 +# +#======================================================================== + +use strict; +use warnings; +use lib qw( ./lib ../lib ); +use File::Temp qw( tempdir ); +use File::Path qw( mkpath rmtree ); +use File::Spec; +use Template; +use Test::More tests => 8; + +# Set up temp directories for source and compiled templates +my $srcdir = tempdir( CLEANUP => 1 ); +my $compdir = tempdir( CLEANUP => 1 ); + +my $src_file = File::Spec->catfile($srcdir, 'hello.tt'); +my $comp_ext = '.ttc'; + +# Step 1: Write the initial template source +write_file($src_file, '[% "Hello, original" %]'); + +# Step 2: Create a Template object with COMPILE_DIR + COMPILE_EXT +my $tt = Template->new({ + INCLUDE_PATH => $srcdir, + COMPILE_DIR => $compdir, + COMPILE_EXT => $comp_ext, + STAT_TTL => 0, # always stat the file +}); +ok($tt, 'Template object created'); + +# Step 3: Process the template for the first time (triggers _fetch + _compile with filename) +my $output = ''; +ok($tt->process('hello.tt', {}, \$output), 'first process succeeds') + || diag $tt->error; +is($output, 'Hello, original', 'first output is correct'); + +# Step 4: Verify compiled file was written to disk +my $comp_file = find_compiled_file($compdir, 'hello.tt' . $comp_ext); +ok($comp_file && -f $comp_file, 'compiled file exists on disk after first compile'); + +# Record the compiled file's content for later comparison +my $compiled_content_v1 = read_file($comp_file); + +# Step 5: Modify the source template (with a newer mtime) +# Sleep briefly to ensure the mtime changes +sleep(1); +write_file($src_file, '[% "Hello, updated" %]'); + +# Step 6: Process the template again — this should trigger _refresh() which recompiles +$output = ''; +ok($tt->process('hello.tt', {}, \$output), 'second process succeeds after source change') + || diag $tt->error; +is($output, 'Hello, updated', 'second output reflects updated source'); + +# Step 7: Verify the compiled file on disk was also updated +my $compiled_content_v2 = read_file($comp_file); +isnt($compiled_content_v2, $compiled_content_v1, + 'compiled file on disk was updated after source changed'); +like($compiled_content_v2, qr/updated/, + 'compiled file contains the new template content'); + +# -- helpers -- + +sub write_file { + my ($path, $content) = @_; + open(my $fh, '>', $path) or die "Cannot write $path: $!"; + print $fh $content; + close($fh); +} + +sub read_file { + my ($path) = @_; + return undef unless $path && -f $path; + open(my $fh, '<', $path) or die "Cannot read $path: $!"; + local $/; + my $content = <$fh>; + close($fh); + return $content; +} + +sub find_compiled_file { + my ($dir, $suffix) = @_; + # The compiled file path is $COMPILE_DIR + $src_path + $COMPILE_EXT + # We need to find it recursively since the full source path is embedded + my @found; + _find_files($dir, $suffix, \@found); + return $found[0]; +} + +sub _find_files { + my ($dir, $suffix, $found) = @_; + opendir(my $dh, $dir) or return; + while (my $entry = readdir($dh)) { + next if $entry eq '.' || $entry eq '..'; + my $path = File::Spec->catfile($dir, $entry); + if (-d $path) { + _find_files($path, $suffix, $found); + } + elsif ($path =~ /\Q$suffix\E$/) { + push @$found, $path; + } + } + closedir($dh); +}