Skip to content

Commit

Permalink
Fix DotvvmConfiguration freezing
Browse files Browse the repository at this point in the history
  • Loading branch information
exyi committed Dec 19, 2023
1 parent d7d6533 commit 7a473c8
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ public class DefaultControlResolver : ControlResolverBase
private readonly DotvvmConfiguration configuration;
private readonly IControlBuilderFactory controlBuilderFactory;
private readonly CompiledAssemblyCache compiledAssemblyCache;
private readonly Dictionary<string, Type>? controlNameMappings;

private static object locker = new object();
private static bool isInitialized = false;
private static object dotvvmLocker = new object();
private static bool isDotvvmInitialized = false;

private static Dictionary<string, Type>? controlNameMappings;

public DefaultControlResolver(DotvvmConfiguration configuration, IControlBuilderFactory controlBuilderFactory, CompiledAssemblyCache compiledAssemblyCache) : base(configuration.Markup)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private void ThrowIfFrozen()
public void Freeze()
{
isFrozen = true;
Diagnostics.Freeze();
Markup.Freeze();
RouteTable.Freeze();
Resources.Freeze();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void Validate()
private void ThrowIfFrozen()
{
if (isFrozen)
FreezableUtils.Error(nameof(DotvvmControlConfiguration));
throw FreezableUtils.Error(nameof(DotvvmControlConfiguration));
}
public void Freeze()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Framework/Configuration/DotvvmFeatureFlag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public bool IsEnabledForRoute(string? routeName)
private void ThrowIfFrozen()
{
if (isFrozen)
FreezableUtils.Error($"{nameof(DotvvmFeatureFlag)} {this.FlagName}");
throw FreezableUtils.Error($"{nameof(DotvvmFeatureFlag)} {this.FlagName}");
}
public void Freeze()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void Disable()
private void ThrowIfFrozen()
{
if (isFrozen)
FreezableUtils.Error($"{nameof(DotvvmGlobalFeatureFlag)} {this.FlagName}");
throw FreezableUtils.Error($"{nameof(DotvvmGlobalFeatureFlag)} {this.FlagName}");
}

public void Freeze()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ public void Freeze()
foreach (var t in this.HtmlAttributeTransforms)
t.Value.Freeze();
_defaultDirectives.Freeze();

FreezableList.Freeze(ref _importedNamespaces);
JavascriptTranslator.Freeze();
FreezableList.Freeze(ref _defaultExtensionParameters);
}
}
}
110 changes: 110 additions & 0 deletions src/Tests/Runtime/ConfigurationValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DotVVM.Framework.Testing;
using DotVVM.Framework.Routing;
using DotVVM.Framework.ResourceManagement;
using System.Threading.Tasks;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Compilation.Styles;

namespace DotVVM.Framework.Tests.Runtime
{
Expand Down Expand Up @@ -62,6 +66,112 @@ public void FeatureFlag_InvalidExclude()
XAssert.Equal("Cannot exclude route 'a' because the feature flag myFlag is disabled by default.", e.Message);
}

[TestMethod]
public void FeatureFlag3State_InvalidExclude()
{
var flag = new Dotvvm3StateFeatureFlag("myFlag");
flag.Enabled = false;
var e = XAssert.ThrowsAny<Exception>(() => flag.ExcludeRoute("a"));
XAssert.Equal("Cannot exclude route 'a' because the feature flag myFlag is disabled by default.", e.Message);
}

[TestMethod]
public void FeatureFlag3State_ValidOperations()
{
var flag = new Dotvvm3StateFeatureFlag("myFlag");

Assert.IsNull(flag.Enabled);
Assert.IsNull(flag.IsEnabledForRoute("a"));
Assert.IsTrue(flag.IsEnabledForRoute("a", true));

flag.EnableForAllRoutes();
Assert.IsTrue(flag.Enabled);
Assert.IsTrue(flag.IsEnabledForRoute("a", false));
XAssert.Empty(flag.IncludedRoutes);
XAssert.Empty(flag.ExcludedRoutes);

flag.ExcludeRoute("a");
Assert.IsTrue(flag.Enabled);
XAssert.Contains("a", flag.ExcludedRoutes);
Assert.IsFalse(flag.IsEnabledForRoute("a"));
Assert.IsTrue(flag.IsEnabledForRoute("b"));

flag.DisableForAllRoutes()
.IncludeRoute("a")
.IncludeRoute("b");
Assert.IsFalse(flag.Enabled);
XAssert.Contains("a", flag.IncludedRoutes);
XAssert.Contains("b", flag.IncludedRoutes);
XAssert.Empty(flag.ExcludedRoutes);

flag.EnableForRoutes("x", "y");
Assert.IsFalse(flag.Enabled);
XAssert.Equal(new [] { "x", "y" }, flag.IncludedRoutes);
XAssert.Empty(flag.ExcludedRoutes);

flag.Reset().IncludeRoute("always-enabled").ExcludeRoute("always-disabled");
Assert.IsNull(flag.IsEnabledForRoute("a"));
Assert.IsTrue(flag.IsEnabledForRoute("always-enabled"));
Assert.IsFalse(flag.IsEnabledForRoute("always-disabled"));
}

[TestMethod]
public void FeatureFlagGlobal3State_ValidOperations()
{
var flag = new DotvvmGlobal3StateFeatureFlag("myFlag");
Assert.IsNull(flag.Enabled);
flag.Enable();
Assert.IsTrue(flag.Enabled);
flag.Disable();
Assert.IsFalse(flag.Enabled);
flag.Reset();
Assert.IsNull(flag.Enabled);
flag.Freeze();
XAssert.ThrowsAny<Exception>(() => flag.Enabled = true);
}

[TestMethod]
public void Freezing()
{
var config = DotvvmTestHelper.CreateConfiguration();
config.Freeze();
XAssert.ThrowsAny<Exception>(() => config.RouteTable.Add("a", "a", null, null, presenterFactory: _ => throw new NotImplementedException()));
XAssert.ThrowsAny<Exception>(() => config.RouteTable.AddRouteRedirection("b", "url", "a"));
XAssert.ThrowsAny<Exception>(() => config.ApplicationPhysicalPath = "a");
XAssert.ThrowsAny<Exception>(() => config.Debug = true);
XAssert.ThrowsAny<Exception>(() => config.DefaultCulture = "a");
XAssert.ThrowsAny<Exception>(() => config.Diagnostics.PerfWarnings.BigViewModelBytes = 1);
XAssert.ThrowsAny<Exception>(() => config.Diagnostics.PerfWarnings.IsEnabled = false);
XAssert.ThrowsAny<Exception>(() => config.Diagnostics.CompilationPage.AuthorizationPredicate = _ => Task.FromResult(true));
XAssert.ThrowsAny<Exception>(() => config.Diagnostics.CompilationPage.RouteName = "a");
XAssert.ThrowsAny<Exception>(() => config.Resources.DefaultResourceProcessors.Add(new SpaModeResourceProcessor(config)));
// adding resources is actually explicitly allowed as they are stored in a ConcurrentDictionary
config.Resources.Register("my-test-resource", new InlineScriptResource("alert(1)"));
XAssert.ThrowsAny<Exception>(() => config.RouteConstraints.Add("a", new GenericRouteParameterType(_ => "..", (_, _) => new ParameterParseResult(true, "yes"))));
XAssert.ThrowsAny<Exception>(() => config.Markup.Assemblies.Add("aa"));
XAssert.ThrowsAny<Exception>(() => config.Markup.ViewCompilation.CompileInParallel = false);
XAssert.ThrowsAny<Exception>(() => config.Markup.ImportedNamespaces.Add(new("ns1")));
XAssert.ThrowsAny<Exception>(() => config.Markup.DefaultDirectives.Add(new("import", "ABC")));
XAssert.ThrowsAny<Exception>(() => config.Markup.HtmlAttributeTransforms.Remove(new HtmlTagAttributePair { TagName = "a", AttributeName = "href" }));
XAssert.ThrowsAny<Exception>(() => config.Runtime.ReloadMarkupFiles.Enable());
XAssert.ThrowsAny<Exception>(() => config.Runtime.GlobalFilters.Add(null));
XAssert.ThrowsAny<Exception>(() => config.Security.ContentTypeOptionsHeader.IncludedRoutes.Add("abc"));
XAssert.ThrowsAny<Exception>(() => config.Security.ContentTypeOptionsHeader.ExcludedRoutes.Add("abc"));
XAssert.ThrowsAny<Exception>(() => config.Security.FrameOptionsCrossOrigin.Enabled = true);
XAssert.ThrowsAny<Exception>(() => config.Security.FrameOptionsSameOrigin.Enabled = true);
XAssert.ThrowsAny<Exception>(() => config.Security.FrameOptionsSameOrigin.Enabled = true);
XAssert.ThrowsAny<Exception>(() => config.Security.RequireSecFetchHeaders.Enabled = true);
XAssert.ThrowsAny<Exception>(() => config.Security.VerifySecFetchForCommands.Enabled = true);
XAssert.ThrowsAny<Exception>(() => config.Security.XssProtectionHeader.Enabled = true);
XAssert.ThrowsAny<Exception>(() => config.Security.ReferrerPolicyValue = "aa");
XAssert.ThrowsAny<Exception>(() => config.Security.SessionIdCookieName = "aa");
XAssert.ThrowsAny<Exception>(() => config.Styles.RegisterForTag("div").SetAttribute("data-a", "b"));
XAssert.ThrowsAny<Exception>(() => config.Styles.Styles = new List<IStyle>());
XAssert.ThrowsAny<Exception>(() => config.Styles.Styles.RemoveAt(0));
XAssert.ThrowsAny<Exception>(() => config.Styles.Styles.Add(null));

}

[TestMethod]
public void ValidateMissingRoutes()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using DotVVM.Framework.Binding.Expressions;
using System.Threading.Tasks;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Testing;

namespace DotVVM.Framework.Tests.Runtime.ControlTree
{
Expand Down Expand Up @@ -57,29 +58,31 @@ @baseType controlAlias
[TestMethod]
public void ResolvedTree_BaseTypeDirective_CorrectBindings_UsingGlobalAliasedType()
{
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport($"DotVVM.Framework.Tests.Runtime.ControlTree.{nameof(TestControl)}", "controlAlias"));
var root = ParseSource(@$"
var config = DotvvmTestHelper.CreateConfiguration();
config.Markup.ImportedNamespaces.Add(new NamespaceImport($"DotVVM.Framework.Tests.Runtime.ControlTree.{nameof(TestControl)}", "controlAlias"));
var root = DotvvmTestHelper.ParseResolvedTree(@$"
@viewModel object
@baseType controlAlias
<dot:TextBox Text={{value: _control.MyProperty }} />
<dot:Button Click={{staticCommand: _control.MyCommand()}} Text=""Test"" />
");
", configuration: config);
CheckServiceAndBinding(root);
}

[TestMethod]
public void ResolvedTree_BaseTypeDirective_CorrectBindings_UsingGlobalImportedNamespace()
{
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport("DotVVM.Framework.Tests.Runtime.ControlTree"));
var config = DotvvmTestHelper.CreateConfiguration();
config.Markup.ImportedNamespaces.Add(new NamespaceImport("DotVVM.Framework.Tests.Runtime.ControlTree"));

var root = ParseSource(@$"
var root = DotvvmTestHelper.ParseResolvedTree(@$"
@viewModel object
@baseType {nameof(TestControl)}
<dot:TextBox Text={{value: _control.MyProperty }} />
<dot:Button Click={{staticCommand: _control.MyCommand()}} Text=""Test"" />
");
", configuration: config);
CheckServiceAndBinding(root);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DotVVM.Framework.Utils;
using System;
using DotVVM.Framework.Testing;
using System.Collections.Generic;
using DotVVM.Framework.Compilation;

Expand Down Expand Up @@ -103,18 +104,19 @@ @property List<int> Items
[TestMethod]
public void ResolvedTree_MarkupDeclaredProperty_GlobalImportUsed()
{
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport("System.Collections.Generic"));
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport("System"));
var config = DotvvmTestHelper.CreateConfiguration();
config.Markup.ImportedNamespaces.Add(new NamespaceImport("System.Collections.Generic"));
config.Markup.ImportedNamespaces.Add(new NamespaceImport("System"));

var root = ParseSource(@$"
var root = DotvvmTestHelper.ParseResolvedTree(@$"
@viewModel object
@property List<Guid> Items
<dot:Repeater DataSource={{value: _control.Items}}>
<li>{{{{value: _this}}}}</li>
</dot:Repeater>
",
fileName: "control.dotcontrol");
fileName: "control.dotcontrol", configuration: config);

CheckPropertyAndBinding(root, typeof(List<Guid>), "Items");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using DotVVM.Framework.Tests.Runtime.ControlTree.DefaultControlTreeResolver;
using DotVVM.Framework.Compilation.ControlTree;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Testing;

namespace DotVVM.Framework.Tests.Runtime.ControlTree
{
Expand Down Expand Up @@ -52,30 +53,32 @@ @viewModel object
[TestMethod]
public void ResolvedTree_ServiceDirective_CorrectBindingFromInjectedService_UsingGlobalImportedAliasedNamespace()
{
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport(
var config = DotvvmTestHelper.CreateConfiguration();
config.Markup.ImportedNamespaces.Add(new NamespaceImport(
$"DotVVM.Framework.Tests.Runtime.ControlTree.DefaultControlTreeResolver.{nameof(TestService)}",
"testServiceAlias"));

var root = ParseSource(@$"
var root = DotvvmTestHelper.ParseResolvedTree(@$"
@viewModel object
@service testService = testServiceAlias
<dot:Button Click={{staticCommand: testService.TestCall()}} Text=""Test"" />
");
", configuration: config);
CheckServiceAndBinding(root);
}

[TestMethod]
public void ResolvedTree_ServiceDirective_CorrectBindingFromInjectedService_UsingGlobalImportedNamespace()
{
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport($"DotVVM.Framework.Tests.Runtime.ControlTree.DefaultControlTreeResolver"));
var config = DotvvmTestHelper.CreateConfiguration();
config.Markup.ImportedNamespaces.Add(new NamespaceImport($"DotVVM.Framework.Tests.Runtime.ControlTree.DefaultControlTreeResolver"));

var root = ParseSource(@$"
var root = DotvvmTestHelper.ParseResolvedTree(@$"
@viewModel object
@service testService = {nameof(TestService)}
<dot:Button Click={{staticCommand: testService.TestCall()}} Text=""Test"" />
");
", configuration: config);
CheckServiceAndBinding(root);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DotVVM.Framework.Compilation.Binding;
using DotVVM.Framework.Compilation;
using DotVVM.Framework.Testing;

namespace DotVVM.Framework.Tests.Runtime.ControlTree
{
Expand Down Expand Up @@ -70,9 +71,10 @@ @viewModel viewModelAlias
[TestMethod]
public void ResolvedTree_ViewModel_TypeFromGlobalImportedAliasedType()
{
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport("DotVVM.Framework.Tests.Runtime.ControlTree.TestViewModel", "viewModelAlias"));
var config = DotvvmTestHelper.CreateConfiguration();
config.Markup.ImportedNamespaces.Add(new NamespaceImport("DotVVM.Framework.Tests.Runtime.ControlTree.TestViewModel", "viewModelAlias"));

var root = ParseSource(@"@viewModel viewModelAlias");
var root = DotvvmTestHelper.ParseResolvedTree(@"@viewModel viewModelAlias", configuration: config);

Assert.IsFalse(root.Directives.Any(d => d.Value.Any(dd => dd.DothtmlNode.HasNodeErrors)));
Assert.AreEqual(typeof(TestViewModel), root.DataContextTypeStack.DataContextType);
Expand All @@ -81,9 +83,10 @@ public void ResolvedTree_ViewModel_TypeFromGlobalImportedAliasedType()
[TestMethod]
public void ResolvedTree_ViewModel_TypeFromGlobalImportedNamespace()
{
configuration.Markup.ImportedNamespaces.Add(new NamespaceImport("DotVVM.Framework.Tests.Runtime.ControlTree"));
var config = DotvvmTestHelper.CreateConfiguration();
config.Markup.ImportedNamespaces.Add(new NamespaceImport("DotVVM.Framework.Tests.Runtime.ControlTree"));

var root = ParseSource(@"@viewModel TestViewModel");
var root = DotvvmTestHelper.ParseResolvedTree(@"@viewModel TestViewModel", configuration: config);

Assert.IsFalse(root.Directives.Any(d => d.Value.Any(dd => dd.DothtmlNode.HasNodeErrors)));
Assert.AreEqual(typeof(TestViewModel), root.DataContextTypeStack.DataContextType);
Expand Down

0 comments on commit 7a473c8

Please sign in to comment.