|
| 1 | +using Fluid.Ast; |
| 2 | +using Fluid.Parser; |
| 3 | +using Fluid.Tests.Mocks; |
| 4 | +using Fluid.Values; |
| 5 | +using Microsoft.Extensions.FileProviders; |
1 | 6 | using System;
|
2 | 7 | using System.Collections.Generic;
|
3 | 8 | using System.IO;
|
4 | 9 | using System.Linq;
|
| 10 | +using System.Runtime.InteropServices; |
5 | 11 | using System.Text.Encodings.Web;
|
| 12 | +using System.Threading; |
6 | 13 | using System.Threading.Tasks;
|
7 |
| -using Fluid.Ast; |
8 |
| -using Fluid.Parser; |
9 |
| -using Fluid.Tests.Mocks; |
10 |
| -using Fluid.Values; |
11 | 14 | using Xunit;
|
| 15 | +using Xunit.Abstractions; |
| 16 | +using Xunit.Sdk; |
12 | 17 |
|
13 | 18 | namespace Fluid.Tests
|
14 | 19 | {
|
@@ -433,71 +438,168 @@ public void IncludeTag_Caches_Template(bool useExtension)
|
433 | 438 | [Fact]
|
434 | 439 | public void IncludeTag_Caches_ParsedTemplate()
|
435 | 440 | {
|
436 |
| - var templates = "abcdefg".Select(x => new string(x, 10)).ToArray(); |
| 441 | + var templates = new Dictionary<string, string> |
| 442 | + { |
| 443 | + ["a.liquid"] = "content1", |
| 444 | + ["folder/a.liquid"] = "content2", |
| 445 | + ["folder/b.liquid"] = "content3", |
| 446 | + ["folder/c.liquid"] = "content4", |
| 447 | + ["folder/other/d.liquid"] = "content5", |
| 448 | + ["b.liquid"] = "content6", |
| 449 | + ["c.liquid"] = "content7", |
| 450 | + ["d.liquid"] = "content8", |
| 451 | + }; |
437 | 452 |
|
438 |
| - var fileProvider = new MockFileProvider(); |
| 453 | + var tempPath = Path.Combine(Path.GetTempPath(), "FluidTests", Path.GetRandomFileName()); |
| 454 | + Directory.CreateDirectory(tempPath); |
439 | 455 |
|
440 |
| - foreach (var t in templates) |
441 |
| - { |
442 |
| - fileProvider.Add($"{t[0]}.liquid", t); |
443 |
| - } |
| 456 | + var fileProvider = new PhysicalFileProvider(tempPath); |
| 457 | + |
| 458 | + WriteFilesContent(templates, tempPath); |
444 | 459 |
|
445 |
| - var fileInfos = templates.Select(x => fileProvider.GetFileInfo($"{x[0]}.liquid")).Cast<MockFileInfo>().ToArray(); |
| 460 | + var fileInfos = templates.ToDictionary(t => t.Key, t => fileProvider.GetFileInfo(t.Key)); |
446 | 461 |
|
447 | 462 | var options = new TemplateOptions() { FileProvider = fileProvider, MemberAccessStrategy = UnsafeMemberAccessStrategy.Instance };
|
448 | 463 | _parser.TryParse("{%- include file -%}", out var template);
|
449 | 464 |
|
450 | 465 | // The first time a template is included it will be read from the file provider
|
451 |
| - foreach (var f in fileInfos) |
| 466 | + foreach (var t in templates) |
452 | 467 | {
|
453 |
| - var filename = f.Name; |
454 |
| - |
455 |
| - Assert.False(f.Accessed); |
| 468 | + var f = fileProvider.GetFileInfo(t.Key); |
456 | 469 |
|
457 | 470 | var context = new TemplateContext(options);
|
458 |
| - context.SetValue("file", filename); |
| 471 | + context.SetValue("file", t.Key); |
459 | 472 | var result = template.Render(context);
|
460 | 473 |
|
461 |
| - Assert.True(f.Accessed); |
462 |
| - } |
| 474 | + Assert.Equal(t.Value, result); |
463 | 475 |
|
464 |
| - foreach (var f in fileInfos) |
465 |
| - { |
466 |
| - f.Accessed = false; |
| 476 | + Assert.True(options.TemplateCache.TryGetTemplate(t.Key, f.LastModified, out var cachedTemplate)); |
467 | 477 | }
|
468 | 478 |
|
469 | 479 | // The next time a template is included it should not be accessed from the file provider but cached instead
|
470 |
| - foreach (var f in fileInfos) |
| 480 | + foreach (var t in templates) |
471 | 481 | {
|
472 |
| - var filename = f.Name; |
| 482 | + var f = fileProvider.GetFileInfo(t.Key); |
473 | 483 |
|
474 |
| - Assert.False(f.Accessed); |
| 484 | + options.TemplateCache.SetTemplate(t.Key, f.LastModified, new MockFluidTemplate(t.Key)); |
475 | 485 |
|
476 | 486 | var context = new TemplateContext(options);
|
477 |
| - context.SetValue("file", filename); |
| 487 | + context.SetValue("file", t.Key); |
478 | 488 | var result = template.Render(context);
|
479 | 489 |
|
480 |
| - Assert.False(f.Accessed); |
| 490 | + Assert.Equal(t.Key, result); |
481 | 491 | }
|
482 | 492 |
|
483 |
| - foreach (var f in fileInfos) |
484 |
| - { |
485 |
| - f.LastModified = DateTime.UtcNow; |
486 |
| - } |
| 493 | + // Update the files so they are accessed again |
| 494 | + WriteFilesContent(templates, tempPath); |
| 495 | + |
| 496 | + Thread.Sleep(1000); // Wait for the file provider to update the last modified date |
487 | 497 |
|
488 | 498 | // If the attributes have changed then the template should be reloaded
|
489 |
| - foreach (var f in fileInfos) |
| 499 | + foreach (var t in templates) |
490 | 500 | {
|
491 |
| - var filename = f.Name; |
492 |
| - |
493 |
| - Assert.False(f.Accessed); |
| 501 | + var f = fileProvider.GetFileInfo(t.Key); |
494 | 502 |
|
495 | 503 | var context = new TemplateContext(options);
|
496 |
| - context.SetValue("file", filename); |
| 504 | + context.SetValue("file", t.Key); |
497 | 505 | var result = template.Render(context);
|
498 | 506 |
|
499 |
| - Assert.True(f.Accessed); |
| 507 | + Assert.Equal(t.Value, result); |
500 | 508 | }
|
| 509 | + |
| 510 | + static void WriteFilesContent(Dictionary<string, string> templates, string tempPath) |
| 511 | + { |
| 512 | + foreach (var t in templates) |
| 513 | + { |
| 514 | + Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(tempPath, t.Key))); |
| 515 | + File.WriteAllText(Path.Combine(tempPath, t.Key), t.Value); |
| 516 | + } |
| 517 | + } |
| 518 | + } |
| 519 | + |
| 520 | + [Fact] |
| 521 | + public void IncludeTag_Caches_DifferentFolders() |
| 522 | + { |
| 523 | + var tempPath = Path.Combine(Path.GetTempPath(), "FluidTests", Path.GetRandomFileName()); |
| 524 | + Directory.CreateDirectory(tempPath); |
| 525 | + |
| 526 | + Directory.CreateDirectory(tempPath + "/this-folder"); |
| 527 | + Directory.CreateDirectory(tempPath + "/this-folder/that-folder"); |
| 528 | + |
| 529 | + var fileProvider = new PhysicalFileProvider(tempPath); |
| 530 | + |
| 531 | + File.WriteAllText(tempPath + "/this-folder/this_file.liquid", "content1"); |
| 532 | + File.WriteAllText(tempPath + "/this-folder/that-folder/this_file.liquid", "content2"); |
| 533 | + |
| 534 | + var options = new TemplateOptions() { FileProvider = fileProvider }; |
| 535 | + _parser.TryParse("{%- include file -%}", out var template); |
| 536 | + |
| 537 | + var context = new TemplateContext(options); |
| 538 | + context.SetValue("file", "this-folder/this_file.liquid"); |
| 539 | + |
| 540 | + Assert.Equal("content1", template.Render(context)); |
| 541 | + |
| 542 | + context.SetValue("file", "this-folder/that-folder/this_file.liquid"); |
| 543 | + |
| 544 | + Assert.Equal("content2", template.Render(context)); |
| 545 | + |
| 546 | + try |
| 547 | + { |
| 548 | + Directory.Delete(tempPath, true); |
| 549 | + } |
| 550 | + catch |
| 551 | + { |
| 552 | + // Ignore any exceptions |
| 553 | + } |
| 554 | + } |
| 555 | + |
| 556 | + [Fact] |
| 557 | + public void IncludeTag_Caches_HandleFileSystemCasing() |
| 558 | + { |
| 559 | + // We can't rely on the OS to detect if the FS is case sensitive or not. c.f. MacOS |
| 560 | + string file = Path.GetTempPath() + Guid.NewGuid().ToString().ToLower(); |
| 561 | + File.CreateText(file).Close(); |
| 562 | + bool isCaseInsensitiveFilesystem = File.Exists(file.ToUpper()); |
| 563 | + File.Delete(file); |
| 564 | + |
| 565 | + var tempPath = Path.Combine(Path.GetTempPath(), "FluidTests", Path.GetRandomFileName()); |
| 566 | + Directory.CreateDirectory(tempPath); |
| 567 | + |
| 568 | + var fileProvider = new PhysicalFileProvider(tempPath); |
| 569 | + |
| 570 | + File.WriteAllText(tempPath + "/this_file.liquid", "content1"); |
| 571 | + File.WriteAllText(tempPath + "/This_file.liquid", "content2"); |
| 572 | + |
| 573 | + var options = new TemplateOptions() { FileProvider = fileProvider }; |
| 574 | + _parser.TryParse("{%- include file -%}", out var template); |
| 575 | + |
| 576 | + var context = new TemplateContext(options); |
| 577 | + |
| 578 | + if (isCaseInsensitiveFilesystem) |
| 579 | + { |
| 580 | + // Windows is case insensitive, there should be only one file |
| 581 | + context.SetValue("file", "this_file.liquid"); |
| 582 | + Assert.Equal("content2", template.Render(context)); |
| 583 | + context.SetValue("file", "THIS_FILE.liquid"); |
| 584 | + Assert.Equal("content2", template.Render(context)); |
| 585 | + } |
| 586 | + else |
| 587 | + { |
| 588 | + // Linux is case sensitive, this should be a new cache entry |
| 589 | + context.SetValue("file", "this_file.liquid"); |
| 590 | + Assert.Equal("content1", template.Render(context)); |
| 591 | + context.SetValue("file", "This_file.liquid"); |
| 592 | + Assert.Equal("content2", template.Render(context)); |
| 593 | + } |
| 594 | + |
| 595 | + try |
| 596 | + { |
| 597 | + Directory.Delete(tempPath, true); |
| 598 | + } |
| 599 | + catch |
| 600 | + { |
| 601 | + // Ignore any exceptions |
| 602 | + } |
501 | 603 | }
|
502 | 604 | }
|
503 | 605 | }
|
0 commit comments