提交 4332186d 编写于 作者: J Jared Parsons

fixing suites

上级 b1c3a128
......@@ -20,6 +20,7 @@
<Compile Include="Metadata\WinMdDumpTest.cs" />
<Compile Include="Metadata\WinMdEventTests.cs" />
<Compile Include="Metadata\WinMdMetadataTests.cs" />
<Compile Include="WinRTUtil.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Test\Utilities\Desktop\TestUtilities.Desktop.csproj">
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using static Microsoft.CodeAnalysis.Test.Utilities.CommonTestBase;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
internal static class WinRTUtil
{
internal static CompilationVerifier CompileAndVerifyOnWin8Only(
this CSharpTestBase testBase,
string source,
MetadataReference[] additionalRefs = null,
string expectedOutput = null)
{
var isWin8 = OSVersion.IsWin8;
return testBase.CompileAndVerifyWinRt(
source,
additionalRefs: additionalRefs,
expectedOutput: isWin8 ? expectedOutput : null,
verify: isWin8);
}
}
}
......@@ -16,7 +16,8 @@
<NoStdLib>true</NoStdLib>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
<ItemGroup>
<None Include="project.json" />
......@@ -33,6 +34,7 @@
<Compile Include="Extensions.cs" />
<Compile Include="LocalVariableDeclaratorsCollector.cs" />
<Compile Include="MetadataTestHelpers.cs" />
<Compile Include="MockCSharpCompiler.cs" />
<Compile Include="ScriptTestFixtures.cs" />
<Compile Include="SemanticModelTestBase.cs" />
<Compile Include="SymbolUtilities.cs" />
......@@ -80,4 +82,4 @@
<UserProperties project_1json__JSONSchema="http://json.schemastore.org/launchsettings" />
</VisualStudio>
</ProjectExtensions>
</Project>
</Project>
\ No newline at end of file
......@@ -13,14 +13,16 @@
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.UnitTests;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Test.MetadataUtilities;
using Roslyn.Utilities;
using Xunit;
using Roslyn.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities.CodeRuntime;
using System.Globalization;
namespace Microsoft.CodeAnalysis.CSharp.Test.Utilities
{
......@@ -75,6 +77,7 @@ private Action<IModuleSymbol> Translate(Action<ModuleSymbol> action)
string expectedOutput = null,
CompilationOptions options = null,
ParseOptions parseOptions = null,
EmitOptions emitOptions = null,
bool verify = true)
{
return base.CompileAndVerify(
......@@ -88,9 +91,39 @@ private Action<IModuleSymbol> Translate(Action<ModuleSymbol> action)
expectedOutput: expectedOutput,
options: options,
parseOptions: parseOptions,
emitOptions: emitOptions,
verify: verify);
}
internal CompilationVerifier CompileAndVerify(
string[] sources,
MetadataReference[] additionalRefs = null,
IEnumerable<ModuleData> dependencies = null,
Action<ModuleSymbol> sourceSymbolValidator = null,
Action<PEAssembly> validator = null,
Action<ModuleSymbol> symbolValidator = null,
SignatureDescription[] expectedSignatures = null,
string expectedOutput = null,
CompilationOptions options = null,
ParseOptions parseOptions = null,
EmitOptions emitOptions = null,
bool verify = true)
{
return base.CompileAndVerify(
sources,
additionalRefs,
dependencies,
Translate2(sourceSymbolValidator),
validator,
Translate2(symbolValidator),
expectedSignatures,
expectedOutput,
options,
parseOptions,
emitOptions,
verify);
}
internal CompilationVerifier CompileAndVerifyExperimental(
string source,
MessageID feature,
......@@ -141,6 +174,30 @@ private Action<IModuleSymbol> Translate(Action<ModuleSymbol> action)
expectedOutput: expectedOutput,
verify: verify);
}
internal CompilationVerifier CompileAndVerify(
Compilation compilation,
IEnumerable<ResourceDescription> manifestResources = null,
IEnumerable<ModuleData> dependencies = null,
Action<ModuleSymbol> sourceSymbolValidator = null,
Action<PEAssembly> validator = null,
Action<ModuleSymbol> symbolValidator = null,
SignatureDescription[] expectedSignatures = null,
string expectedOutput = null,
bool verify = true)
{
return base.CompileAndVerify(
compilation,
manifestResources,
dependencies,
Translate2(sourceSymbolValidator),
validator,
Translate2(symbolValidator),
expectedSignatures,
expectedOutput,
null,
verify);
}
}
public abstract class CSharpTestBaseBase : CommonTestBase
......@@ -185,7 +242,17 @@ public static SyntaxTree Parse(string text, string filename = "", CSharpParseOpt
}
var stringText = StringText.From(text, Encoding.UTF8);
return SyntaxFactory.ParseSyntaxTree(stringText, options, filename);
return CheckSerializable(SyntaxFactory.ParseSyntaxTree(stringText, options, filename));
}
private static SyntaxTree CheckSerializable(SyntaxTree tree)
{
var stream = new MemoryStream();
var root = tree.GetRoot();
root.SerializeTo(stream);
stream.Position = 0;
var deserializedRoot = CSharpSyntaxNode.DeserializeFrom(stream);
return tree;
}
public static SyntaxTree[] Parse(IEnumerable<string> sources, CSharpParseOptions options = null)
......@@ -267,6 +334,23 @@ public static SyntaxTree ParseWithRoundTripCheck(string text, CSharpParseOptions
return CreateCompilation(source, refs, options, assemblyName);
}
public static CSharpCompilation CreateCompilationWithMscorlib46(
string source,
IEnumerable<MetadataReference> references = null,
CSharpCompilationOptions options = null,
CSharpParseOptions parseOptions = null,
string sourceFileName = "",
string assemblyName = "")
{
return CreateCompilationWithMscorlib46(
new[] { source },
references,
options,
parseOptions,
sourceFileName,
assemblyName);
}
public static CSharpCompilation CreateCompilationWithMscorlib46(
string[] sources,
IEnumerable<MetadataReference> references = null,
......@@ -341,9 +425,7 @@ public static SyntaxTree ParseWithRoundTripCheck(string text, CSharpParseOptions
refs.AddRange(references);
}
refs.Add(MscorlibRef_v4_0_30316_17626);
var parseOptions = TestOptions.Regular.WithExperimental(feature);
return CreateCompilation(new[] { Parse(text, sourceFileName, parseOptions) }, refs, options, assemblyName);
return CreateCompilation(new[] { Parse(text, sourceFileName, TestOptions.Regular.WithExperimental(feature)) }, refs, options, assemblyName);
}
internal static CSharpCompilation CreateExperimentalCompilationWithMscorlib45(
......@@ -360,18 +442,22 @@ public static SyntaxTree ParseWithRoundTripCheck(string text, CSharpParseOptions
refs.AddRange(references);
}
refs.Add(MscorlibRef_v4_0_30316_17626);
var parseOptions = TestOptions.Regular.WithExperimental(feature);
return CreateCompilation((from text in texts select Parse(text, sourceFileName, parseOptions)).ToArray(), refs, options, assemblyName);
return CreateCompilation((from text in texts select Parse(text, sourceFileName, TestOptions.Regular.WithExperimental(feature))).ToArray(), refs, options, assemblyName);
}
public static CSharpCompilation CreateCompilationWithMscorlib45AndCSruntime(
string text,
CSharpCompilationOptions options = null,
CSharpParseOptions parseOptions = null)
CSharpParseOptions parseOptions = null,
MetadataReference[] additionalRefs = null)
{
var refs = new List<MetadataReference>() { MscorlibRef_v4_0_30316_17626, SystemCoreRef, CSharpRef };
if (additionalRefs != null)
{
refs.AddRange(additionalRefs);
}
return CreateCompilation(new[] { Parse(text, options: parseOptions) }, refs, options);
}
......@@ -478,16 +564,6 @@ public static SyntaxTree ParseWithRoundTripCheck(string text, CSharpParseOptions
options = options.WithConcurrentBuild(false);
}
#if Test_IOperation_Interface
// Create a compilation for the purpose of verifying operation tree only,
// so this won't interfere with test.
var compilationForOperationWalking = CSharpCompilation.Create(
assemblyName == "" ? GetUniqueName() : assemblyName,
trees,
references,
options);
WalkOperationTree(compilationForOperationWalking);
#endif
return CSharpCompilation.Create(
assemblyName == "" ? GetUniqueName() : assemblyName,
trees,
......@@ -503,50 +579,13 @@ public static SyntaxTree ParseWithRoundTripCheck(string text, CSharpParseOptions
CSharpParseOptions parseOptions = null)
{
var trees = (sources == null) ? null : sources.Select(s => Parse(s, options: parseOptions)).ToArray();
var compilationOptions = options ?? TestOptions.ReleaseDll;
#if Test_IOperation_Interface
// Create a compilation for the purpose of verifying operation tree only,
// so this won't interfere with test.
var compilationForOperationWalking = CSharpCompilation.Create(
identity.Name,
options: compilationOptions,
references: references,
syntaxTrees: trees);
WalkOperationTree(compilationForOperationWalking);
#endif
var c = CSharpCompilation.Create(identity.Name, options: compilationOptions, references: references, syntaxTrees: trees);
var c = CSharpCompilation.Create(identity.Name, options: options ?? TestOptions.ReleaseDll, references: references, syntaxTrees: trees);
Assert.NotNull(c.Assembly); // force creation of SourceAssemblySymbol
((SourceAssemblySymbol)c.Assembly).lazyAssemblyIdentity = identity;
return c;
}
#if Test_IOperation_Interface
private static void WalkOperationTree(CSharpCompilation compilation)
{
var operationWalker = TestOperationWalker.GetInstance();
foreach (var tree in compilation.SyntaxTrees)
{
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
// TODO: check other operation root as well (property, etc.)
foreach (BaseMethodDeclarationSyntax methodNode in root.DescendantNodesAndSelf().OfType<BaseMethodDeclarationSyntax>())
{
var bodyNode = methodNode.Body;
if (bodyNode != null)
{
var operation = semanticModel.GetOperation(bodyNode);
operationWalker.Visit(operation);
}
}
}
}
#endif
public static CSharpCompilation CreateSubmissionWithExactReferences(
string code,
IEnumerable<MetadataReference> references = null,
......@@ -585,6 +624,33 @@ private static void WalkOperationTree(CSharpCompilation compilation)
globalsType: hostObjectType);
}
public CompilationVerifier CompileWithCustomILSource(string cSharpSource, string ilSource, Action<CSharpCompilation> compilationVerifier = null, bool importInternals = true, string expectedOutput = null)
{
var compilationOptions = (expectedOutput != null) ? TestOptions.ReleaseExe : TestOptions.ReleaseDll;
if (importInternals)
{
compilationOptions = compilationOptions.WithMetadataImportOptions(MetadataImportOptions.Internal);
}
if (ilSource == null)
{
var c = CreateCompilationWithMscorlib(cSharpSource, options: compilationOptions);
return CompileAndVerify(c, expectedOutput: expectedOutput);
}
MetadataReference reference = null;
using (var tempAssembly = IlasmUtilities.CreateTempAssembly(ilSource))
{
reference = MetadataReference.CreateFromImage(ReadFromFile(tempAssembly.Path));
}
var compilation = CreateCompilationWithMscorlib(cSharpSource, new[] { reference }, compilationOptions);
compilationVerifier?.Invoke(compilation);
return CompileAndVerify(compilation, expectedOutput: expectedOutput);
}
protected override Compilation GetCompilationForEmit(
IEnumerable<string> source,
IEnumerable<MetadataReference> additionalRefs,
......@@ -618,7 +684,7 @@ private static void WalkOperationTree(CSharpCompilation compilation)
CompileAndVerify(comp, expectedOutput: ""); //need expected output to force execution
Assert.False(true, string.Format("Expected exception {0}({1})", typeof(T).Name, expectedMessage));
}
catch (Exception x)
catch (ExecutionException x)
{
var e = x.InnerException;
Assert.IsType<T>(e);
......@@ -791,32 +857,42 @@ internal static string GetDocumentationCommentText(CSharpCompilation compilation
using (MemoryStream stream = new MemoryStream())
{
DiagnosticBag diagnostics = DiagnosticBag.GetInstance();
CultureInfo saveUICulture = null;
var ensureObject = ensureEnglishUICulture ? new EnsureEnglishUICulture() : null;
using (ensureObject)
if (ensureEnglishUICulture)
{
DocumentationCommentCompiler.WriteDocumentationCommentXml(compilation, outputName, stream, diagnostics, default(CancellationToken), filterTree, filterSpanWithinTree);
var preferred = EnsureEnglishUICulture.PreferredOrNull;
if (preferred == null)
{
ensureEnglishUICulture = false;
}
else
{
saveUICulture = CultureInfo.CurrentUICulture;
CultureInfo.CurrentUICulture = preferred;
}
}
if (expectedDiagnostics != null)
try
{
diagnostics.Verify(expectedDiagnostics);
DocumentationCommentCompiler.WriteDocumentationCommentXml(compilation, outputName, stream, diagnostics, default(CancellationToken), filterTree, filterSpanWithinTree);
}
diagnostics.Free();
byte[] buffer;
ArraySegment<byte> bufferSegment;
if (stream.TryGetBuffer(out bufferSegment) &&
bufferSegment.Count == bufferSegment.Array.Length)
finally
{
buffer = bufferSegment.Array;
if (ensureEnglishUICulture)
{
CultureInfo.CurrentUICulture = saveUICulture;
}
}
else
if (expectedDiagnostics != null)
{
buffer = stream.ToArray();
diagnostics.Verify(expectedDiagnostics);
}
diagnostics.Free();
string text = Encoding.UTF8.GetString(buffer);
string text = Encoding.UTF8.GetString(stream.ToArray());
int length = text.IndexOf('\0');
if (length >= 0)
{
......@@ -891,12 +967,6 @@ internal unsafe static string VisualizeRealIL(PEModuleSymbol peModule, Compilati
return sb.ToString();
}
internal static string VisualizeIL(CompilationTestData testData, Func<MethodSymbol, bool> predicate)
{
var builder = testData.GetIL(m => predicate((MethodSymbol)m));
return ILBuilderVisualizer.ILBuilderToString(builder);
}
private static string GetContainingTypeMetadataName(IMethodSymbol method)
{
var type = method.ContainingType;
......
......@@ -5,6 +5,7 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
......@@ -12,7 +13,6 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Xunit;
using System.Reflection;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Diagnostics;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Test.Utilities
{
internal class MockCSharpCompiler : CSharpCompiler
{
private readonly ImmutableArray<DiagnosticAnalyzer> _analyzers;
internal Compilation Compilation;
public MockCSharpCompiler(string responseFile, string baseDirectory, string[] args)
: this(responseFile, baseDirectory, args, ImmutableArray<DiagnosticAnalyzer>.Empty)
{
}
public MockCSharpCompiler(string responseFile, string workingDirectory, string[] args, ImmutableArray<DiagnosticAnalyzer> analyzers)
: base(CSharpCommandLineParser.Default, responseFile, args, CreateBuildPaths(workingDirectory), Environment.GetEnvironmentVariable("LIB"), new DesktopAnalyzerAssemblyLoader())
{
_analyzers = analyzers;
}
private static BuildPaths CreateBuildPaths(string workingDirectory)
{
return new BuildPaths(
clientDir: Path.GetDirectoryName(CorLightup.Desktop.GetAssemblyLocation(typeof(CSharpCompiler).GetTypeInfo().Assembly)),
workingDir: workingDirectory,
sdkDir: RuntimeEnvironment.GetRuntimeDirectory(),
tempDir: Path.GetTempPath());
}
protected override ImmutableArray<DiagnosticAnalyzer> ResolveAnalyzersFromArguments(
List<DiagnosticInfo> diagnostics,
CommonMessageProvider messageProvider)
{
var analyzers = base.ResolveAnalyzersFromArguments(diagnostics, messageProvider);
if (!_analyzers.IsDefaultOrEmpty)
{
analyzers = analyzers.InsertRange(0, _analyzers);
}
return analyzers;
}
public override Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger, ErrorLogger errorLogger)
{
Compilation = base.CreateCompilation(consoleOutput, touchedFilesLogger, errorLogger);
return Compilation;
}
}
}
{
"dependencies": {
"System.Runtime.Extensions": "4.3.0"
},
"frameworks": {
"netstandard1.3": {
......
......@@ -43,7 +43,6 @@
<ItemGroup>
<None Include="project.json" />
<Compile Include="CoreCLRRuntimeEnvironment.cs" />
<Compile Include="Exceptions.cs" />
</ItemGroup>
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
</Project>
</Project>
\ No newline at end of file
......@@ -304,16 +304,30 @@ public IList<ModuleData> GetAllModuleData()
public void PeVerify()
{
var emitData = GetEmitData();
emitData.RuntimeData.PeverifyRequested = true;
emitData.Manager.PeVerifyModules(new[] { emitData.MainModule.FullName });
try
{
var emitData = GetEmitData();
emitData.RuntimeData.PeverifyRequested = true;
emitData.Manager.PeVerifyModules(new[] { emitData.MainModule.FullName });
}
catch (RuntimePeVerifyException e)
{
throw new PeVerifyException(e.Output, e.ExePath);
}
}
public string[] PeVerifyModules(string[] modulesToVerify, bool throwOnError = true)
{
var emitData = GetEmitData();
emitData.RuntimeData.PeverifyRequested = true;
return emitData.Manager.PeVerifyModules(modulesToVerify, throwOnError);
try
{
var emitData = GetEmitData();
emitData.RuntimeData.PeverifyRequested = true;
return emitData.Manager.PeVerifyModules(modulesToVerify, throwOnError);
}
catch (RuntimePeVerifyException e)
{
throw new PeVerifyException(e.Output, e.ExePath);
}
}
public SortedSet<string> GetMemberSignaturesFromMetadata(string fullyQualifiedTypeName, string memberName)
......
......@@ -9,39 +9,30 @@
namespace Microsoft.CodeAnalysis.Test.Utilities.CodeRuntime
{
[Serializable]
public class EmitException : Exception
public class RuntimePeVerifyException : Exception
{
public IEnumerable<Diagnostic> Diagnostics { get; }
public string Output { get; }
public string ExePath { get; }
protected EmitException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
public EmitException(IEnumerable<Diagnostic> diagnostics, string directory)
: base(GetMessageFromResult(diagnostics, directory))
protected RuntimePeVerifyException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
this.Diagnostics = diagnostics;
Output = info.GetString(nameof(Output));
ExePath = info.GetString(nameof(ExePath));
}
}
[Serializable]
public class PeVerifyException : Exception
{
protected PeVerifyException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
public PeVerifyException(string output, string exePath)
: base(GetMessageFromResult(output, exePath)) { }
}
[Serializable]
public class ExecutionException : Exception
{
public ExecutionException(string expectedOutput, string actualOutput, string exePath)
: base(GetMessageFromResult(expectedOutput, actualOutput, exePath)) { }
public ExecutionException(Exception innerException, string exePath)
: base(GetMessageFromException(innerException, exePath), innerException) { }
public RuntimePeVerifyException(string output, string exePath)
: base(GetMessageFromResult(output, exePath))
{
Output = output;
ExePath = exePath;
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(Output), Output);
info.AddValue(nameof(ExePath), ExePath);
}
}
}
......@@ -442,7 +442,7 @@ public string[] PeVerifyModules(string[] modulesToVerify, bool throwOnError = tr
{
string dumpDir;
RuntimeUtilities.DumpAssemblyData(ModuleDatas, out dumpDir);
throw new PeVerifyException(errors.ToString(), dumpDir);
throw new RuntimePeVerifyException(errors.ToString(), dumpDir);
}
return allOutput.ToArray();
}
......
......@@ -2,30 +2,36 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Microsoft.CodeAnalysis;
using static Roslyn.Test.Utilities.ExceptionHelper;
namespace Microsoft.CodeAnalysis.Test.Utilities
namespace Microsoft.CodeAnalysis.Test.Utilities.CodeRuntime
{
public sealed class EmitException : Exception
public class EmitException : Exception
{
public IEnumerable<Diagnostic> Diagnostics { get; }
internal EmitException(IEnumerable<Diagnostic> diagnostics, string directory)
public EmitException(IEnumerable<Diagnostic> diagnostics, string directory)
: base(GetMessageFromResult(diagnostics, directory))
{
this.Diagnostics = diagnostics;
}
}
public sealed class PeVerifyException : Exception
public class PeVerifyException : Exception
{
internal PeVerifyException(string output, string exePath) : base(GetMessageFromResult(output, exePath)) { }
public PeVerifyException(string output, string exePath)
: base(GetMessageFromResult(output, exePath)) { }
}
public sealed class ExecutionException : Exception
public class ExecutionException : Exception
{
internal ExecutionException(string expectedOutput, string actualOutput, string exePath) : base(GetMessageFromResult(expectedOutput, actualOutput, exePath)) { }
public ExecutionException(string expectedOutput, string actualOutput, string exePath)
: base(GetMessageFromResult(expectedOutput, actualOutput, exePath)) { }
internal ExecutionException(Exception innerException, string exePath) : base(GetMessageFromException(innerException, exePath), innerException) { }
public ExecutionException(Exception innerException, string exePath)
: base(GetMessageFromException(innerException, exePath), innerException) { }
}
}
......@@ -124,6 +124,7 @@
<Compile Include="Compilation\CompilationOutputFiles.cs" />
<Compile Include="Compilation\CompilationTestDataExtensions.cs" />
<Compile Include="Compilation\DiagnosticBagErrorLogger.cs" />
<Compile Include="Compilation\Exceptions.cs" />
<Compile Include="Compilation\IRuntimeEnvironment.cs" />
<Compile Include="Compilation\OperationTreeVerifier.cs" />
<Compile Include="Compilation\TestOperationWalker.cs" />
......@@ -223,4 +224,4 @@
</ItemGroup>
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
<ProjectExtensions />
</Project>
</Project>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册