Skip to content

Commit 28a89e2

Browse files
Added tests and got them working
1 parent fae097a commit 28a89e2

File tree

6 files changed

+206
-70
lines changed

6 files changed

+206
-70
lines changed

src/System.CommandLine.Subsystems.Tests/System.CommandLine.Subsystems.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
-->
3333
<Compile Include="AlternateSubsystems.cs" />
3434
<Compile Include="Constants.cs" />
35+
<Compile Include="ValueSourceTests.cs" />
3536
<Compile Include="ValidationSubsystemTests.cs" />
3637
<Compile Include="ValueSubsystemTests.cs" />
3738
<Compile Include="ResponseSubsystemTests.cs" />

src/System.CommandLine.Subsystems.Tests/ValidationSubsystemTests.cs

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -185,52 +185,4 @@ public void Values_above_relative_upper_bound_report_error()
185185
var error = pipelineResult.GetErrors().First();
186186
// TODO: Create test mechanism for CliDiagnostics
187187
}
188-
189-
[Fact]
190-
public void Values_below_environment_lower_bound_report_error()
191-
{
192-
var envName = "SYSTEM_COMMANDLINE_LOWERBOUND";
193-
Environment.SetEnvironmentVariable(envName, "2");
194-
var option = GetOptionWithRangeBounds(ValueSource<int>.Create(envName, s => int.Parse(s) + 1), 50);
195-
196-
var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 2");
197-
Environment.SetEnvironmentVariable(envName, null);
198-
199-
pipelineResult.Should().NotBeNull();
200-
pipelineResult.GetErrors().Should().HaveCount(1);
201-
var error = pipelineResult.GetErrors().First();
202-
// TODO: Create test mechanism for CliDiagnostics
203-
}
204-
205-
206-
[Fact]
207-
public void Values_within_environment_range_do_not_report_error()
208-
{
209-
var envName = "SYSTEM_COMMANDLINE_LOWERBOUND";
210-
Environment.SetEnvironmentVariable(envName, "2");
211-
var option = GetOptionWithRangeBounds(ValueSource<int>.Create(envName, s => int.Parse(s) + 1), 50);
212-
213-
var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 11");
214-
Environment.SetEnvironmentVariable(envName, null);
215-
216-
pipelineResult.Should().NotBeNull();
217-
pipelineResult.GetErrors().Should().BeEmpty();
218-
}
219-
220-
[Fact]
221-
public void Values_above_environment_upper_bound_report_error()
222-
{
223-
var envName = "SYSTEM_COMMANDLINE_LOWERBOUND";
224-
Environment.SetEnvironmentVariable(envName, "2");
225-
var option = GetOptionWithRangeBounds(0, ValueSource<int>.Create(envName, s => int.Parse(s) + 1));
226-
227-
var pipelineResult = ExecutedPipelineResultForRangeOption(option, "--intOpt 4");
228-
Environment.SetEnvironmentVariable(envName, null);
229-
230-
pipelineResult.Should().NotBeNull();
231-
pipelineResult.GetErrors().Should().HaveCount(1);
232-
var error = pipelineResult.GetErrors().First();
233-
// TODO: Create test mechanism for CliDiagnostics
234-
}
235-
236188
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using FluentAssertions;
5+
using Microsoft.VisualBasic.FileIO;
6+
using System.CommandLine.Parsing;
7+
using System.CommandLine.ValueConditions;
8+
using Xunit;
9+
10+
namespace System.CommandLine.Subsystems.Tests;
11+
12+
public class ValueSourceTests
13+
{
14+
private PipelineResult EmptyPipelineResult(string input = "", params CliValueSymbol[] valueSymbols)
15+
{
16+
var rootCommand = new CliRootCommand();
17+
foreach (var symbol in valueSymbols)
18+
{
19+
rootCommand.Add(symbol);
20+
}
21+
var parseResult = CliParser.Parse(rootCommand, input);
22+
return new PipelineResult(parseResult, "", Pipeline.CreateEmpty());
23+
}
24+
25+
[Fact]
26+
public void SimpleValueSource_with_set_value_retrieved()
27+
{
28+
var valueSource = new SimpleValueSource<int>(42);
29+
30+
int value = valueSource.GetTypedValue(EmptyPipelineResult());
31+
32+
value.Should()
33+
.Be(42);
34+
}
35+
36+
[Fact]
37+
public void SimpleValueSource_with_converted_value_retrieved()
38+
{
39+
ValueSource<int> valueSource = 42;
40+
41+
int value = valueSource.GetTypedValue(EmptyPipelineResult());
42+
43+
value.Should()
44+
.Be(42);
45+
}
46+
47+
[Fact]
48+
public void SimpleValueSource_created_via_extension_value_retrieved()
49+
{
50+
var valueSource = ValueSource.Create(42);
51+
52+
int value = valueSource.GetTypedValue(EmptyPipelineResult());
53+
54+
value.Should()
55+
.Be(42);
56+
}
57+
58+
[Fact]
59+
public void CalculatedValueSource_produces_value()
60+
{
61+
var valueSource = new CalculatedValueSource<int>(() => 42);
62+
63+
int value = valueSource.GetTypedValue(EmptyPipelineResult());
64+
65+
value.Should()
66+
.Be(42);
67+
}
68+
69+
[Fact]
70+
public void CalculatedValueSource_implicitly_converted_produces_value()
71+
{
72+
// TODO: Figure out why this doesn't work, and remove implicit operator if it does not work
73+
// ValueSource<int> valueSource2 = (() => 42);
74+
ValueSource<int> valueSource = (ValueSource<int>)(() => 42);
75+
76+
int value = valueSource.GetTypedValue(EmptyPipelineResult());
77+
78+
value.Should()
79+
.Be(42);
80+
}
81+
82+
[Fact]
83+
public void CalculatedValueSource_from_extension_produces_value()
84+
{
85+
var valueSource = ValueSource.Create(() => 42);
86+
int value = valueSource.GetTypedValue(EmptyPipelineResult());
87+
88+
value.Should()
89+
.Be(42);
90+
}
91+
92+
[Fact]
93+
public void RelativeToSymbolValueSource_produces_value_that_was_set()
94+
{
95+
var option = new CliOption<int>("-a");
96+
var valueSource = new RelativeToSymbolValueSource<int>(option);
97+
98+
int value = valueSource.GetTypedValue(EmptyPipelineResult("-a 42", option));
99+
100+
value.Should()
101+
.Be(42);
102+
}
103+
104+
[Fact]
105+
public void RelativeToSymbolValueSource_implicitly_converted_produces_value_that_was_set()
106+
{
107+
var option = new CliOption<int>("-a");
108+
ValueSource<int> valueSource = option;
109+
110+
int value = valueSource.GetTypedValue(EmptyPipelineResult("-a 42", option));
111+
112+
value.Should()
113+
.Be(42);
114+
}
115+
116+
[Fact]
117+
public void RelativeToSymbolValueSource_from_extension_produces_value_that_was_set()
118+
{
119+
var option = new CliOption<int>("-a");
120+
var valueSource = new RelativeToSymbolValueSource<int>(option);
121+
122+
int value = valueSource.GetTypedValue(EmptyPipelineResult("-a 42", option));
123+
124+
value.Should()
125+
.Be(42);
126+
}
127+
128+
[Fact]
129+
public void RelativeToEnvironmentVariableValueSource_produces_value_that_was_set()
130+
{
131+
var envName = "SYSTEM_COMMANDLINE_TESTING";
132+
var valueSource = new RelativeToEnvironmentVariableValueSource<int>(envName);
133+
134+
Environment.SetEnvironmentVariable(envName, "42");
135+
int value = valueSource.GetTypedValue(EmptyPipelineResult(""));
136+
Environment.SetEnvironmentVariable(envName, null);
137+
138+
value.Should()
139+
.Be(42);
140+
}
141+
142+
143+
[Fact]
144+
public void RelativeToEnvironmentVariableValueSource_from_extension_produces_value_that_was_set()
145+
{
146+
var envName = "SYSTEM_COMMANDLINE_TESTING";
147+
var valueSource = ValueSource.CreateFromEnvironmentVariable<int>(envName);
148+
149+
Environment.SetEnvironmentVariable(envName, "42");
150+
int value = valueSource.GetTypedValue(EmptyPipelineResult(""));
151+
Environment.SetEnvironmentVariable(envName, null);
152+
153+
value.Should()
154+
.Be(42);
155+
}
156+
}

src/System.CommandLine.Subsystems/PipelineResult.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ public class PipelineResult(ParseResult parseResult, string rawInput, Pipeline?
2020
public bool AlreadyHandled { get; set; }
2121
public int ExitCode { get; set; }
2222

23-
public T? GetValue<T>(CliValueSymbol dataSymbol)
23+
public T GetValue<T>(CliValueSymbol dataSymbol)
2424
=> valueProvider.GetValue<T>(dataSymbol);
2525

26-
public object? GetValue(CliValueSymbol option)
27-
=> valueProvider.GetValue<object?>(option);
26+
public object GetValue(CliValueSymbol option)
27+
=> valueProvider.GetValue<object>(option);
2828

2929
public CliValueResult? GetValueResult(CliValueSymbol dataSymbol)
3030
=> ParseResult.GetValueResult(dataSymbol);

src/System.CommandLine.Subsystems/ValueConditions/ValueSource.cs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ public abstract class ValueSource
1111

1212
// TODO: Should we use ToString() here?
1313
public abstract string Description { get; }
14+
public static ValueSource<T> Create<T>(T value, string? description = null)
15+
=> new SimpleValueSource<T>(value, description);
16+
17+
public static ValueSource<T> Create<T>(Func<T> calculation, string? description = null)
18+
=> new CalculatedValueSource<T>(calculation);
19+
20+
public static ValueSource<T> Create<T>(CliValueSymbol otherSymbol, Func<object, T>? calculation = null, string? description = null)
21+
=> new RelativeToSymbolValueSource<T>(otherSymbol, calculation, description);
22+
23+
public static ValueSource<T> CreateFromEnvironmentVariable<T>(string environmentVariableName, Func<string?, T>? calculation = null, string? description = null)
24+
=> new RelativeToEnvironmentVariableValueSource<T>(environmentVariableName, calculation, description);
1425
}
1526

1627
public abstract class ValueSource<T> : ValueSource
@@ -24,18 +35,8 @@ public abstract class ValueSource<T> : ValueSource
2435

2536
public static implicit operator ValueSource<T>(T value) => new SimpleValueSource<T>(value);
2637
public static implicit operator ValueSource<T>(Func<T> calculated) => new CalculatedValueSource<T>(calculated);
27-
28-
public static ValueSource<T> Create(T value, string? description = null)
29-
=> new SimpleValueSource<T>(value, description);
30-
31-
public static ValueSource<T> Create(Func<T> calculation, string? description = null)
32-
=> new CalculatedValueSource<T>(calculation);
33-
34-
public static ValueSource<T> Create(CliValueSymbol otherSymbol, Func<object, T> calculation, string? description = null)
35-
=> new RelativeToSymbolValueSource<T>(otherSymbol, calculation, description);
36-
37-
public static ValueSource<T> Create(string environmentVariableName, Func<string, T> calculation, string? description = null)
38-
=> new RelativeToEnvironmentVariableValueSource<T>(environmentVariableName, calculation, description);
38+
public static implicit operator ValueSource<T>(CliValueSymbol symbol) => new RelativeToSymbolValueSource<T>(symbol);
39+
// Environment variable does not have an explicit operator, because converting to string was too broad
3940
}
4041

4142
public class SimpleValueSource<T>(T value, string? description = null)
@@ -57,21 +58,45 @@ public override T GetTypedValue(PipelineResult pipelineResult)
5758
=> calculation();
5859
}
5960

60-
public class RelativeToSymbolValueSource<T>(CliValueSymbol otherSymbol, Func<object, T> calculation, string? description)
61+
public class RelativeToSymbolValueSource<T>(CliValueSymbol otherSymbol,
62+
Func<object, T>? calculation = null,
63+
string? description = null)
6164
: ValueSource<T>
6265
{
6366
public override string Description { get; } = description;
6467

6568
public override T GetTypedValue(PipelineResult pipelineResult)
66-
=> calculation(pipelineResult.GetValue(otherSymbol));
69+
=> calculation is null
70+
? pipelineResult.GetValue<T>(otherSymbol)
71+
: calculation(pipelineResult.GetValue(otherSymbol));
6772
}
6873

69-
public class RelativeToEnvironmentVariableValueSource<T>(string environmentVariableName, Func<string, T> calculation, string? description)
74+
public class RelativeToEnvironmentVariableValueSource<T>(string environmentVariableName,
75+
Func<string?, T>? calculation = null,
76+
string? description = null)
7077
: ValueSource<T>
7178
{
7279
public override string Description { get; } = description;
7380

7481
public override T GetTypedValue(PipelineResult pipelineResult)
75-
=> calculation(Environment.GetEnvironmentVariable(environmentVariableName));
82+
{
83+
string? stringValue = Environment.GetEnvironmentVariable(environmentVariableName);
84+
85+
if (stringValue is null)
86+
{
87+
// This feels wrong. It isn't saying "Hey, you asked for a value that was not there"
88+
return default;
89+
}
90+
91+
// TODO: What is the best way to do this?
92+
T value = default(T) switch
93+
{
94+
int i => (T)(object)Convert.ToInt32(stringValue),
95+
_ => throw new NotImplementedException("Looking for a non-dumb way to do this")
96+
};
97+
return calculation is null
98+
? value
99+
: calculation(Environment.GetEnvironmentVariable(environmentVariableName));
100+
}
76101
}
77102

src/System.CommandLine.Subsystems/ValueProvider.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ private bool TryGetValue<T>(CliSymbol symbol, out T? value)
3434
return false;
3535
}
3636

37-
public T? GetValue<T>(CliValueSymbol valueSymbol)
37+
public T GetValue<T>(CliValueSymbol valueSymbol)
3838
=> GetValueInternal<T>(valueSymbol);
3939

40-
private T? GetValueInternal<T>(CliValueSymbol? valueSymbol)
40+
private T GetValueInternal<T>(CliValueSymbol? valueSymbol)
4141
{
42+
// TODO: This method is definitely WRONG. If there is a relative or env variable, it does not continue if it is not found
43+
// TODO: Replace this method with an AggregateValueSource
4244
// NOTE: We use the subsystem's TryGetAnnotation here instead of the GetDefaultValue etc
4345
// extension methods, as the subsystem's TryGetAnnotation respects its annotation provider
4446
return valueSymbol switch
@@ -59,7 +61,7 @@ private bool TryGetValue<T>(CliSymbol symbol, out T? value)
5961
_ => UseValue(valueSymbol, default(T))
6062
};
6163

62-
TValue? UseValue<TValue>(CliSymbol symbol, TValue? value)
64+
TValue UseValue<TValue>(CliSymbol symbol, TValue value)
6365
{
6466
SetValue(symbol, value);
6567
return value;

0 commit comments

Comments
 (0)