Skip to content

Commit 6b35af0

Browse files
authored
Merge pull request #1 from tocsoft/clocktimer
clock timer implementations and abstractions
2 parents f1a9273 + 38df0e9 commit 6b35af0

25 files changed

Lines changed: 1583 additions & 272 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ You probably aren't using DateTime.Now in you code so how can this library help?
4444
| `DateTimeOffset.UtcNow`| `ClockOffset.UtcNow` |
4545
> We include warnings and code fixes for all these mappings.
4646

47+
In addition to the `DateTime` set of APIs we also include a testable wrapper around using the `Stopwatch` class in the form of the `ClockTimer` as with the `Clock` APIs we support pinning for testing purposes.
48+
4749
### The Team
4850

4951
Lead

samples/.vscode/launch.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
// Use IntelliSense to find out which attributes exist for C# debugging
3+
// Use hover for the description of the existing attributes
4+
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": ".NET Core Launch (console)",
9+
"type": "coreclr",
10+
"request": "launch",
11+
"preLaunchTask": "build",
12+
// If you have changed target frameworks, make sure to update the program path.
13+
"program": "${workspaceFolder}/Scratch/bin/Debug/net5.0/Scratch.dll",
14+
"args": [],
15+
"cwd": "${workspaceFolder}/Scratch",
16+
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17+
"console": "internalConsole",
18+
"stopAtEntry": false
19+
},
20+
{
21+
"name": ".NET Core Attach",
22+
"type": "coreclr",
23+
"request": "attach",
24+
"processId": "${command:pickProcess}"
25+
}
26+
]
27+
}

samples/.vscode/tasks.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/Scratch/Scratch.csproj",
11+
"/property:GenerateFullPaths=true",
12+
"/consoleloggerparameters:NoSummary"
13+
],
14+
"problemMatcher": "$msCompile"
15+
},
16+
{
17+
"label": "publish",
18+
"command": "dotnet",
19+
"type": "process",
20+
"args": [
21+
"publish",
22+
"${workspaceFolder}/Scratch/Scratch.csproj",
23+
"/property:GenerateFullPaths=true",
24+
"/consoleloggerparameters:NoSummary"
25+
],
26+
"problemMatcher": "$msCompile"
27+
},
28+
{
29+
"label": "watch",
30+
"command": "dotnet",
31+
"type": "process",
32+
"args": [
33+
"watch",
34+
"run",
35+
"${workspaceFolder}/Scratch/Scratch.csproj",
36+
"/property:GenerateFullPaths=true",
37+
"/consoleloggerparameters:NoSummary"
38+
],
39+
"problemMatcher": "$msCompile"
40+
}
41+
]
42+
}

samples/Scratch.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30804.86
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scratch", "Scratch\Scratch.csproj", "{1A492952-0268-4169-8DB5-FFDBF57C447A}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{1A492952-0268-4169-8DB5-FFDBF57C447A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{1A492952-0268-4169-8DB5-FFDBF57C447A}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{1A492952-0268-4169-8DB5-FFDBF57C447A}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{1A492952-0268-4169-8DB5-FFDBF57C447A}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {D9E660FD-A557-4BE6-B7F8-E45372BAA31C}
24+
EndGlobalSection
25+
EndGlobal

samples/Scratch/Program.cs

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,48 @@
1-
using System;
2-
using System.Threading;
3-
using Tocsoft.DateTimeAbstractions;
4-
5-
namespace Scratch
6-
{
7-
class Program
8-
{
9-
static void Main(string[] args)
10-
{
11-
12-
Console.WriteLine("Real time");
13-
for (var i = 0; i < 10; i++)
14-
{
15-
logTime();
16-
}
17-
Console.WriteLine();
18-
Console.WriteLine("Pinned");
19-
using (Clock.Pin(new DateTime(2000, 01, 01)))
20-
{
21-
for (var i = 0; i < 10; i++)
22-
{
23-
logTime();
24-
}
25-
}
26-
Console.WriteLine();
27-
28-
Console.WriteLine("Real time");
29-
for (var i = 0; i < 10; i++)
30-
{
31-
logTime();
32-
}
33-
Console.WriteLine();
34-
35-
Console.ReadLine();
36-
}
37-
38-
private static void logTime()
39-
{
40-
Console.WriteLine(Clock.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
41-
Thread.Sleep(100);
42-
43-
}
44-
}
45-
}
1+
using System;
2+
using System.Diagnostics;
3+
using System.Threading;
4+
using Tocsoft.DateTimeAbstractions;
5+
6+
namespace Scratch
7+
{
8+
class Program
9+
{
10+
static void Main(string[] args)
11+
{
12+
Console.WriteLine("Real time");
13+
for (var i = 0; i < 10; i++)
14+
{
15+
logTime();
16+
}
17+
Console.WriteLine();
18+
Console.WriteLine("Pinned");
19+
using (ClockTimer.Pin(TimeSpan.FromMilliseconds(5)))
20+
using (Clock.Pin(new DateTime(2000, 01, 01)))
21+
{
22+
for (var i = 0; i < 10; i++)
23+
{
24+
logTime();
25+
}
26+
}
27+
Console.WriteLine();
28+
29+
Console.WriteLine("Real time");
30+
for (var i = 0; i < 10; i++)
31+
{
32+
logTime();
33+
}
34+
Console.WriteLine();
35+
36+
Console.ReadLine();
37+
}
38+
39+
private static void logTime()
40+
{
41+
var w = ClockTimer.StartNew();
42+
Console.Write(Clock.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
43+
Thread.Sleep(100);
44+
w.Stop();
45+
Console.WriteLine($" - in {w.Elapsed}");
46+
}
47+
}
48+
}

samples/Scratch/Scratch.csproj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
<TargetFramework>net5.0</TargetFramework>
66
</PropertyGroup>
77
<ItemGroup>
8-
<PackageReference Include="Tocsoft.DateTimeAbstractions" Version="1.0.1" />
8+
<ProjectReference Include="..\..\src\Tocsoft.DateTimeAbstractions\Tocsoft.DateTimeAbstractions.csproj" />
9+
<ProjectReference Include="..\..\src\Tocsoft.DateTimeAbstractions.Analyzer\Tocsoft.DateTimeAbstractions.Analyzer.csproj"
10+
OutputItemType="Analyzer"
11+
ReferenceOutputAssembly="false" />
912
</ItemGroup>
1013
</Project>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) Tocsoft and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
8+
using System.Threading;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
using Microsoft.CodeAnalysis.Operations;
14+
15+
namespace Tocsoft.DateTimeAbstractions.Analyzer
16+
{
17+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
18+
public class ClockTimerUsageInvocationAnalyzer : DiagnosticAnalyzer
19+
{
20+
public const string DiagnosticId = "TOCSOFT0003";
21+
22+
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
23+
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
24+
private static readonly LocalizableString Title = new LocalizableResourceString(
25+
nameof(Resources.ClockTimerTitle),
26+
Resources.ResourceManager,
27+
typeof(Resources));
28+
29+
private static readonly LocalizableString InvocationMessageFormat = new LocalizableResourceString(
30+
nameof(Resources.ClockTimerAnalyzer_InvocationMessageFormat),
31+
Resources.ResourceManager,
32+
typeof(Resources));
33+
34+
private static readonly LocalizableString InvocationMessageFormatDirected = new LocalizableResourceString(
35+
nameof(Resources.ClockTimerAnalyzer_InvocationMessageFormat_DirectToClockTimer),
36+
Resources.ResourceManager,
37+
typeof(Resources));
38+
39+
private static readonly LocalizableString Description = new LocalizableResourceString(
40+
nameof(Resources.ClockTimerAnalyzerDescription),
41+
Resources.ResourceManager,
42+
typeof(Resources));
43+
44+
private const string Category = "Testability";
45+
46+
private static DiagnosticDescriptor invocationRule = new DiagnosticDescriptor(
47+
DiagnosticId,
48+
Title,
49+
InvocationMessageFormat,
50+
Category,
51+
DiagnosticSeverity.Warning,
52+
isEnabledByDefault: true,
53+
description: Description);
54+
55+
private static DiagnosticDescriptor invocationRuleDirected = new DiagnosticDescriptor(
56+
DiagnosticId,
57+
Title,
58+
InvocationMessageFormatDirected,
59+
Category,
60+
DiagnosticSeverity.Warning,
61+
isEnabledByDefault: true,
62+
description: Description);
63+
64+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
65+
{
66+
get { return ImmutableArray.Create(invocationRule, invocationRuleDirected); }
67+
}
68+
69+
private static readonly ImmutableArray<string> StopwatchMethods = new[]
70+
{
71+
"StartNew"
72+
}.ToImmutableArray();
73+
74+
public override void Initialize(AnalysisContext context)
75+
{
76+
context.EnableConcurrentExecution();
77+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
78+
79+
context.RegisterCompilationStartAction(compilationStartContext =>
80+
{
81+
Compilation compilation = compilationStartContext.Compilation;
82+
INamedTypeSymbol stopwatchType = compilation.GetTypeByMetadataName("System.Diagnostics.Stopwatch");
83+
INamedTypeSymbol clockTimerType = compilation.GetTypeByMetadataName("Tocsoft.DateTimeAbstractions.ClockTimer");
84+
85+
compilationStartContext.RegisterOperationAction(
86+
operationContext =>
87+
{
88+
IInvocationOperation invocation = (IInvocationOperation)operationContext.Operation;
89+
90+
if (invocation.Type == null || invocation.Type != stopwatchType)
91+
{
92+
return;
93+
}
94+
95+
if (!StopwatchMethods.Contains(invocation.TargetMethod?.Name))
96+
{
97+
return;
98+
}
99+
100+
operationContext.ReportDiagnostic(Diagnostic.Create(
101+
clockTimerType == null ? invocationRule : invocationRuleDirected,
102+
invocation.Syntax.GetLocation(),
103+
stopwatchType.Name,
104+
invocation.TargetMethod.Name,
105+
clockTimerType?.Name));
106+
}, OperationKind.Invocation);
107+
});
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)