From 78d9e68fea8ae5da76f79678480e7f4495b4ac7b Mon Sep 17 00:00:00 2001
From: zerlei <1445089819@qq.com>
Date: Tue, 30 Jul 2024 15:36:42 +0800
Subject: [PATCH] =?UTF-8?q?refactor:=E5=B0=86=E6=AF=8F=E4=B8=AA=E6=B5=8B?=
=?UTF-8?q?=E8=AF=95=E6=96=B9=E6=B3=95=E6=94=B9=E4=B8=BA=E7=9B=B8=E4=BA=92?=
=?UTF-8?q?=E7=8B=AC=E7=AB=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
原因是:
1. 相互独立的单元测试,有助于debug问题,
2. 不需要考虑执行顺序,额外的运行环境等,更加简单。
做的工作是:
1. 去掉了顺序执行,这有助于并发调用,减少测试时间
2. 去掉了测试方法中共享的实例
---
Server/Common/Common.csproj | 4 +
Server/Common/Dir.cs | 2 +-
Server/Common/FileDirOp.cs | 137 ++++++++++++++++--
.../Controllers/LocalServerController.cs | 33 +++++
.../Controllers/WeatherForecastController.cs | 33 -----
Server/LocalServer/LocalSyncServer.cs | 19 +++
Server/LocalServer/LocalSyncServerFactory.cs | 22 +++
Server/LocalServer/Program.cs | 54 +++----
Server/LocalServer/WeatherForecast.cs | 13 --
Server/ServerTest/DirFileOpTest.cs | 52 ++++---
Server/ServerTest/FilesSeed.cs | 12 +-
Server/ServerTest/PriorityOrderer.cs | 41 ------
Server/ServerTest/TestPriorityAttribute.cs | 9 --
13 files changed, 265 insertions(+), 166 deletions(-)
create mode 100644 Server/LocalServer/Controllers/LocalServerController.cs
delete mode 100644 Server/LocalServer/Controllers/WeatherForecastController.cs
create mode 100644 Server/LocalServer/LocalSyncServer.cs
create mode 100644 Server/LocalServer/LocalSyncServerFactory.cs
delete mode 100644 Server/LocalServer/WeatherForecast.cs
delete mode 100644 Server/ServerTest/PriorityOrderer.cs
delete mode 100644 Server/ServerTest/TestPriorityAttribute.cs
diff --git a/Server/Common/Common.csproj b/Server/Common/Common.csproj
index fa71b7a..ca2ca29 100644
--- a/Server/Common/Common.csproj
+++ b/Server/Common/Common.csproj
@@ -6,4 +6,8 @@
enable
+
+
+
+
diff --git a/Server/Common/Dir.cs b/Server/Common/Dir.cs
index 8c50004..6c47f34 100644
--- a/Server/Common/Dir.cs
+++ b/Server/Common/Dir.cs
@@ -465,7 +465,7 @@ public class Dir(string path, List? children = null, NextOpType? nex
{
var ldir = this;
var rdir = other;
- Dir? cDir = new Dir(rdir.FormatedPath);
+ Dir? cDir = new(rdir.FormatedPath);
//分别对文件和文件夹分组
List lFiles = [];
List rFiles = [];
diff --git a/Server/Common/FileDirOp.cs b/Server/Common/FileDirOp.cs
index 8a0cd6b..977ecaa 100644
--- a/Server/Common/FileDirOp.cs
+++ b/Server/Common/FileDirOp.cs
@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
+using ICSharpCode.SharpZipLib.Zip;
using Microsoft.VisualBasic;
namespace Common;
@@ -26,7 +27,8 @@ public abstract class FileDirOpStra
/// 文件目录打包
///
///
-public class FileDirOpForPack(string srcRootPath, string dstRootPath) : FileDirOpStra
+public class FileDirOpForPack(string srcRootPath, string dstRootPath, string syncId = "")
+ : FileDirOpStra
{
///
/// 目标根目录
@@ -38,12 +40,78 @@ public class FileDirOpForPack(string srcRootPath, string dstRootPath) : FileDirO
///
public readonly string SrcRootPath = srcRootPath;
+ public readonly string SyncId = string.IsNullOrEmpty(syncId)
+ ? Guid.NewGuid().ToString()
+ : syncId;
+
///
/// 最终完成时的压缩
///
public void FinallyCompress()
{
- var x = DstRootPath;
+ static List GetFilesResus(string dirPath)
+ {
+ var files = new List();
+ foreach (var file in Directory.GetFiles(dirPath))
+ {
+ files.Add(file);
+ }
+ foreach (var dir in Directory.GetDirectories(dirPath))
+ {
+ foreach (var file in GetFilesResus(dir))
+ {
+ files.Add(file);
+ }
+ }
+ return files;
+ }
+ var fileNames = GetFilesResus(SrcRootPath);
+ var OuptPutFile = Path.GetDirectoryName(DstRootPath) + $"/{SyncId}.zip";
+ using FileStream fsOut = new(OuptPutFile, FileMode.Create);
+ using ZipOutputStream zipStream = new(fsOut);
+ {
+ zipStream.SetLevel(9); // 设置压缩级别
+ zipStream.Password = "VSXsdf.123d7802zw@#4_"; // 设置密码
+ byte[] buffer = new byte[4096];
+
+ foreach (string file in fileNames)
+ {
+ // Using GetFileName makes the result compatible with XP
+ // as the resulting path is not absolute.
+ var entry = new ZipEntry(file.Replace(SrcRootPath, ""));
+
+ // Setup the entry data as required.
+
+ // Crc and size are handled by the library for seakable streams
+ // so no need to do them here.
+
+ // Could also use the last write time or similar for the file.
+ //entry.DateTime = ;
+ entry.DateTime = System.IO.File.GetLastWriteTime(file);
+ zipStream.PutNextEntry(entry);
+
+ using (FileStream fs = System.IO.File.OpenRead(file))
+ {
+ // Using a fixed size buffer here makes no noticeable difference for output
+ // but keeps a lid on memory usage.
+ int sourceBytes;
+ do
+ {
+ sourceBytes = fs.Read(buffer, 0, buffer.Length);
+ zipStream.Write(buffer, 0, sourceBytes);
+ } while (sourceBytes > 0);
+ }
+ }
+
+ // Finish/Close arent needed strictly as the using statement does this automatically
+
+ // Finish is important to ensure trailing information for a Zip file is appended. Without this
+ // the created file would be invalid.
+ zipStream.Finish();
+
+ // Close is important to wrap things up and unlock the file.
+ zipStream.Close();
+ }
}
///
@@ -97,27 +165,70 @@ public class FileDirOpForPack(string srcRootPath, string dstRootPath) : FileDirO
this.FileCreate(absolutePath, mtime);
}
- public override void FileDel(string absolutePath)
- {
- throw new NotImplementedException();
- }
+ public override void FileDel(string absolutePath) { }
- public override void DirDel(Dir dir, bool IsRecursion = true)
- {
- throw new NotImplementedException();
- }
+ public override void DirDel(Dir dir, bool IsRecursion = true) { }
}
-public class FileDirOpForUnpack(string srcCompressedPath, string dstRootPath) : FileDirOpStra
+public class FileDirOpForUnpack(string srcRootPath, string dstRootPath, string syncId)
+ : FileDirOpStra
{
///
/// 解压缩,必须首先调用
///
public void FirstUnComparess()
{
- var x = SrcCompressedPath;
+ string zipFilePath = $"{SrcRootPath}/{SyncId}.zip";
+
+ using (ZipInputStream s = new ZipInputStream(System.IO.File.OpenRead(zipFilePath)))
+ {
+ s.Password = "VSXsdf.123d7802zw@#4_";
+ ZipEntry theEntry;
+ while ((theEntry = s.GetNextEntry()) != null)
+ {
+ Console.WriteLine(theEntry.Name);
+
+ string directoryName =
+ DstRootPath + $"/{SyncId}/" + Path.GetDirectoryName(theEntry.Name)
+ ?? throw new NullReferenceException("无法得到父文件目录!");
+ string fileName = Path.GetFileName(theEntry.Name);
+
+ // create directory
+ if (directoryName.Length > 0)
+ {
+ Directory.CreateDirectory(directoryName);
+ }
+
+ if (fileName != String.Empty)
+ {
+ using (
+ FileStream streamWriter = System.IO.File.Create(
+ directoryName + "/" + fileName
+ )
+ )
+ {
+ int size = 2048;
+ byte[] data = new byte[2048];
+ while (true)
+ {
+ size = s.Read(data, 0, data.Length);
+ if (size > 0)
+ {
+ streamWriter.Write(data, 0, size);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
}
+ public readonly string SyncId = syncId;
+
///
/// 目标根目录
///
@@ -126,7 +237,7 @@ public class FileDirOpForUnpack(string srcCompressedPath, string dstRootPath) :
///
/// 源目录
///
- public readonly string SrcCompressedPath = srcCompressedPath;
+ public readonly string SrcRootPath = srcRootPath;
///
/// 最终完成时的压缩
diff --git a/Server/LocalServer/Controllers/LocalServerController.cs b/Server/LocalServer/Controllers/LocalServerController.cs
new file mode 100644
index 0000000..2f2f7a9
--- /dev/null
+++ b/Server/LocalServer/Controllers/LocalServerController.cs
@@ -0,0 +1,33 @@
+using System.Text;
+using Microsoft.AspNetCore.Mvc;
+
+namespace LocalServer.Controllers
+{
+ public class LocalServerController(LocalSyncServerFactory factory) : ControllerBase
+ {
+ private readonly LocalSyncServerFactory Factory = factory;
+
+ [Route("/")]
+ public async Task WebsocketConnection(string Name)
+ {
+ if (HttpContext.WebSockets.IsWebSocketRequest)
+ {
+ try
+ {
+ var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
+ Factory.CreateLocalSyncServer(webSocket, Name);
+ }
+ catch (Exception e)
+ {
+ HttpContext.Response.Body = new MemoryStream(Encoding.UTF8.GetBytes(e.Message));
+ HttpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable;
+ }
+ }
+ else
+ {
+ HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
+ }
+ }
+ //TODO 是否在本地记载同步日志?
+ }
+}
diff --git a/Server/LocalServer/Controllers/WeatherForecastController.cs b/Server/LocalServer/Controllers/WeatherForecastController.cs
deleted file mode 100644
index f1d4199..0000000
--- a/Server/LocalServer/Controllers/WeatherForecastController.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-
-namespace LocalServer.Controllers
-{
- [ApiController]
- [Route("[controller]")]
- public class WeatherForecastController : ControllerBase
- {
- private static readonly string[] Summaries = new[]
- {
- "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
- };
-
- private readonly ILogger _logger;
-
- public WeatherForecastController(ILogger logger)
- {
- _logger = logger;
- }
-
- [HttpGet(Name = "GetWeatherForecast")]
- public IEnumerable Get()
- {
- return Enumerable.Range(1, 5).Select(index => new WeatherForecast
- {
- Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
- TemperatureC = Random.Shared.Next(-20, 55),
- Summary = Summaries[Random.Shared.Next(Summaries.Length)]
- })
- .ToArray();
- }
- }
-}
diff --git a/Server/LocalServer/LocalSyncServer.cs b/Server/LocalServer/LocalSyncServer.cs
new file mode 100644
index 0000000..a55b662
--- /dev/null
+++ b/Server/LocalServer/LocalSyncServer.cs
@@ -0,0 +1,19 @@
+using System.Net.WebSockets;
+
+namespace LocalServer;
+
+public class LocalSyncServer
+{
+ public readonly WebSocket Socket;
+ public readonly string Name;
+
+ public readonly LocalSyncServerFactory Factory;
+
+ public LocalSyncServer(WebSocket socket, string name, LocalSyncServerFactory factory)
+ {
+ Socket = socket;
+ Name = name;
+ Factory = factory;
+
+ }
+}
diff --git a/Server/LocalServer/LocalSyncServerFactory.cs b/Server/LocalServer/LocalSyncServerFactory.cs
new file mode 100644
index 0000000..b06c64f
--- /dev/null
+++ b/Server/LocalServer/LocalSyncServerFactory.cs
@@ -0,0 +1,22 @@
+using System.Net.WebSockets;
+
+namespace LocalServer;
+
+public class LocalSyncServerFactory
+{
+ public void CreateLocalSyncServer(WebSocket socket, string name)
+ {
+ if (Servers.Select(x => x.Name == name).Any())
+ {
+ throw new Exception("there already is a server with that name is Runing!");
+ }
+ Servers.Add(new LocalSyncServer(socket, name, this));
+ }
+
+ private readonly List Servers = [];
+
+ public void RemoveLocalSyncServer(LocalSyncServer server)
+ {
+ Servers.Remove(server);
+ }
+}
diff --git a/Server/LocalServer/Program.cs b/Server/LocalServer/Program.cs
index c620a67..652ebf8 100644
--- a/Server/LocalServer/Program.cs
+++ b/Server/LocalServer/Program.cs
@@ -1,34 +1,26 @@
+using LocalServer;
+var builder = WebApplication.CreateBuilder(args);
-namespace LocalServer
+// Add services to the container.
+
+builder.Services.AddControllers();
+
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+builder.Services.AddSingleton();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
{
- public class Program
- {
- public static void Main(string[] args)
- {
- var builder = WebApplication.CreateBuilder(args);
-
- // Add services to the container.
-
- builder.Services.AddControllers();
- // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
- builder.Services.AddEndpointsApiExplorer();
- builder.Services.AddSwaggerGen();
-
- var app = builder.Build();
-
- // Configure the HTTP request pipeline.
- if (app.Environment.IsDevelopment())
- {
- app.UseSwagger();
- app.UseSwaggerUI();
- }
-
- app.UseAuthorization();
-
-
- app.MapControllers();
-
- app.Run();
- }
- }
+ app.UseSwagger();
+ app.UseSwaggerUI();
}
+app.UseWebSockets();
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/Server/LocalServer/WeatherForecast.cs b/Server/LocalServer/WeatherForecast.cs
deleted file mode 100644
index a7b4de4..0000000
--- a/Server/LocalServer/WeatherForecast.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace LocalServer
-{
- public class WeatherForecast
- {
- public DateOnly Date { get; set; }
-
- public int TemperatureC { get; set; }
-
- public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-
- public string? Summary { get; set; }
- }
-}
diff --git a/Server/ServerTest/DirFileOpTest.cs b/Server/ServerTest/DirFileOpTest.cs
index 09c13c0..a401fad 100644
--- a/Server/ServerTest/DirFileOpTest.cs
+++ b/Server/ServerTest/DirFileOpTest.cs
@@ -1,31 +1,24 @@
using Common;
+using Xunit;
+
/*using Newtonsoft.Json;*/
-using XUnit.Project.Attributes;
namespace ServerTest;
-///
-/// xUnit将会对每个测试方法创建一个测试上下文,IClassFixture可以用来创建类中共享测试上下文,
-///
-/// XUnit 的测试方法不是按照顺序执行,所以注意对象状态
-///
-/// 一般单元测试,每个测试函数应当是独立的,不让它们按照顺序执行,在一般情况下是最好的做法,参考
-/// https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices
-/// 目前涉及到一些文件的同步,所以按照顺序执行相对较好,这使用了xUnit的方法使它们按照顺序执行
-///
-///
-[TestCaseOrderer(
- ordererTypeName: "XUnit.Project.Orderers.PriorityOrderer",
- ordererAssemblyName: "ServerTest"
-)]
-public class DirFileOpTest(FilesSeed filesSeed) : IClassFixture
+public class DirFileOpTest : IDisposable
{
- private readonly FilesSeed filesSeed = filesSeed;
+ private readonly FilesSeed filesSeed = new();
+
+ public void Dispose()
+ {
+ //filesSeed.Dispose();
+ GC.SuppressFinalize(this);
+ }
///
/// 测试文件目录写入和提取
///
- [Fact, TestPriority(0)]
+ [Fact]
public void FileDirWriteExtract()
{
filesSeed.NewDir.WriteByThisInfo(filesSeed.fileDirOp);
@@ -41,11 +34,10 @@ public class DirFileOpTest(FilesSeed filesSeed) : IClassFixture
///
/// 测试文件差异比较
///
- [Fact, TestPriority(1)]
+ [Fact]
public void FileDirDiff()
{
var cDDir = filesSeed.NewDir.Diff(filesSeed.OldDir);
-
// Console.WriteLine("################################");
// Console.WriteLine(cDDir.Children.Count);
//Assert.True(IsSuccess);
@@ -57,9 +49,10 @@ public class DirFileOpTest(FilesSeed filesSeed) : IClassFixture
///
/// 测试同步是否成功
///
- [Fact, TestPriority(2)]
+ [Fact]
public void SyncFileDir()
{
+ filesSeed.OldDir.WriteByThisInfo(filesSeed.fileDirOp);
filesSeed.OldDir.CombineJustDirFile(filesSeed.fileDirOp, filesSeed.DiffDir);
Dir oldSync = new(filesSeed.OldDir.FormatedPath);
oldSync.ExtractInfo();
@@ -70,7 +63,7 @@ public class DirFileOpTest(FilesSeed filesSeed) : IClassFixture
///
/// 测试文件合并
///
- [Fact, TestPriority(3)]
+ [Fact]
public void DirsCombine()
{
filesSeed.OldDir.CombineJustObject(filesSeed.DiffDir);
@@ -79,4 +72,19 @@ public class DirFileOpTest(FilesSeed filesSeed) : IClassFixture
// Console.WriteLine(filesSeed.OldDir.Path);
Assert.True(filesSeed.OldDir.IsEqual(filesSeed.NewDir), "合并结果不一致!");
}
+
+ [Fact]
+ public void Tt()
+ {
+ filesSeed.NewDir.WriteByThisInfo(filesSeed.fileDirOp);
+ var c = new FileDirOpForPack(filesSeed.NewDir.FormatedPath, filesSeed.TestPath + "/");
+ c.FinallyCompress();
+
+ var d = new FileDirOpForUnpack(
+ filesSeed.TestPath + "/",
+ filesSeed.TestPath + "/",
+ c.SyncId
+ );
+ d.FirstUnComparess();
+ }
}
diff --git a/Server/ServerTest/FilesSeed.cs b/Server/ServerTest/FilesSeed.cs
index 2721a97..c5b37e9 100644
--- a/Server/ServerTest/FilesSeed.cs
+++ b/Server/ServerTest/FilesSeed.cs
@@ -176,7 +176,7 @@ public class FilesSeed : IDisposable
fileDirOp = new SimpleFileDirOp();
}
- private readonly string TestPath = Path.Combine(Directory.GetCurrentDirectory(), "../../..");
+ public readonly string TestPath = Path.Combine(Directory.GetCurrentDirectory(), "../../..");
public Dir NewDir;
public Dir OldDir;
public Dir DiffDir;
@@ -184,8 +184,14 @@ public class FilesSeed : IDisposable
public void Dispose()
{
- Directory.Delete($"{TestPath}/OldDir", true);
- Directory.Delete($"{TestPath}/NewDir", true);
+ if (Directory.Exists($"{TestPath}/OldDir"))
+ {
+ Directory.Delete($"{TestPath}/OldDir", true);
+ }
+ if (Directory.Exists($"{TestPath}/NewDir"))
+ {
+ Directory.Delete($"{TestPath}/NewDir", true);
+ }
Console.WriteLine("FilesSeed Dispose");
GC.SuppressFinalize(this);
}
diff --git a/Server/ServerTest/PriorityOrderer.cs b/Server/ServerTest/PriorityOrderer.cs
deleted file mode 100644
index 519cd6d..0000000
--- a/Server/ServerTest/PriorityOrderer.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Xunit.Abstractions;
-using Xunit.Sdk;
-using XUnit.Project.Attributes;
-
-namespace XUnit.Project.Orderers;
-
-public class PriorityOrderer : ITestCaseOrderer
-{
- public IEnumerable OrderTestCases(
- IEnumerable testCases) where TTestCase : ITestCase
- {
- string assemblyName = typeof(TestPriorityAttribute).AssemblyQualifiedName!;
- var sortedMethods = new SortedDictionary>();
- foreach (TTestCase testCase in testCases)
- {
- int priority = testCase.TestMethod.Method
- .GetCustomAttributes(assemblyName)
- .FirstOrDefault()
- ?.GetNamedArgument(nameof(TestPriorityAttribute.Priority)) ?? 0;
-
- GetOrCreate(sortedMethods, priority).Add(testCase);
- }
-
- foreach (TTestCase testCase in
- sortedMethods.Keys.SelectMany(
- priority => sortedMethods[priority].OrderBy(
- testCase => testCase.TestMethod.Method.Name)))
- {
- Console.WriteLine(testCase);
- yield return testCase;
- }
- }
-
- private static TValue GetOrCreate(
- IDictionary dictionary, TKey key)
- where TKey : struct
- where TValue : new() =>
- dictionary.TryGetValue(key, out TValue? result)
- ? result
- : (dictionary[key] = new TValue());
-}
diff --git a/Server/ServerTest/TestPriorityAttribute.cs b/Server/ServerTest/TestPriorityAttribute.cs
deleted file mode 100644
index 690076b..0000000
--- a/Server/ServerTest/TestPriorityAttribute.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace XUnit.Project.Attributes;
-
-[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
-public class TestPriorityAttribute : Attribute
-{
- public int Priority { get; private set; }
-
- public TestPriorityAttribute(int priority) => Priority = priority;
-}